script/dom/stream/
defaultteeunderlyingsource.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 http://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::jsapi::{HandleValueArray, Heap, NewArrayObject, Value};
10use js::jsval::{ObjectValue, UndefinedValue};
11use js::rust::HandleValue as SafeHandleValue;
12
13use crate::dom::bindings::error::Error;
14use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
15use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
16use crate::dom::globalscope::GlobalScope;
17use crate::dom::promise::Promise;
18use crate::dom::stream::defaultteereadrequest::DefaultTeeReadRequest;
19use crate::dom::stream::readablestreamdefaultreader::ReadRequest;
20use crate::dom::types::{ReadableStream, ReadableStreamDefaultReader};
21use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
22
23#[derive(JSTraceable, MallocSizeOf)]
24pub(crate) enum DefaultTeeCancelAlgorithm {
25    Cancel1Algorithm,
26    Cancel2Algorithm,
27}
28
29#[dom_struct]
30/// <https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee>
31pub(crate) struct DefaultTeeUnderlyingSource {
32    reflector_: Reflector,
33    reader: Dom<ReadableStreamDefaultReader>,
34    stream: Dom<ReadableStream>,
35    branch_1: MutNullableDom<ReadableStream>,
36    branch_2: MutNullableDom<ReadableStream>,
37    #[conditional_malloc_size_of]
38    reading: Rc<Cell<bool>>,
39    #[conditional_malloc_size_of]
40    read_again: Rc<Cell<bool>>,
41    #[conditional_malloc_size_of]
42    canceled_1: Rc<Cell<bool>>,
43    #[conditional_malloc_size_of]
44    canceled_2: Rc<Cell<bool>>,
45    #[conditional_malloc_size_of]
46    clone_for_branch_2: Rc<Cell<bool>>,
47    #[ignore_malloc_size_of = "mozjs"]
48    #[expect(clippy::redundant_allocation)]
49    reason_1: Rc<Box<Heap<Value>>>,
50    #[ignore_malloc_size_of = "mozjs"]
51    #[expect(clippy::redundant_allocation)]
52    reason_2: Rc<Box<Heap<Value>>>,
53    #[conditional_malloc_size_of]
54    cancel_promise: Rc<Promise>,
55    tee_cancel_algorithm: DefaultTeeCancelAlgorithm,
56}
57
58impl DefaultTeeUnderlyingSource {
59    #[expect(clippy::too_many_arguments)]
60    #[expect(clippy::redundant_allocation)]
61    pub(crate) fn new(
62        reader: &ReadableStreamDefaultReader,
63        stream: &ReadableStream,
64        reading: Rc<Cell<bool>>,
65        read_again: Rc<Cell<bool>>,
66        canceled_1: Rc<Cell<bool>>,
67        canceled_2: Rc<Cell<bool>>,
68        clone_for_branch_2: Rc<Cell<bool>>,
69        reason_1: Rc<Box<Heap<Value>>>,
70        reason_2: Rc<Box<Heap<Value>>>,
71        cancel_promise: Rc<Promise>,
72        tee_cancel_algorithm: DefaultTeeCancelAlgorithm,
73        can_gc: CanGc,
74    ) -> DomRoot<DefaultTeeUnderlyingSource> {
75        reflect_dom_object(
76            Box::new(DefaultTeeUnderlyingSource {
77                reflector_: Reflector::new(),
78                reader: Dom::from_ref(reader),
79                stream: Dom::from_ref(stream),
80                branch_1: MutNullableDom::new(None),
81                branch_2: MutNullableDom::new(None),
82                reading,
83                read_again,
84                canceled_1,
85                canceled_2,
86                clone_for_branch_2,
87                reason_1,
88                reason_2,
89                cancel_promise,
90                tee_cancel_algorithm,
91            }),
92            &*stream.global(),
93            can_gc,
94        )
95    }
96
97    pub(crate) fn set_branch_1(&self, stream: &ReadableStream) {
98        self.branch_1.set(Some(stream));
99    }
100
101    pub(crate) fn set_branch_2(&self, stream: &ReadableStream) {
102        self.branch_2.set(Some(stream));
103    }
104
105    /// <https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee>
106    /// Let pullAlgorithm be the following steps:
107    pub(crate) fn pull_algorithm(&self, can_gc: CanGc) -> Rc<Promise> {
108        let cx = GlobalScope::get_cx();
109        // If reading is true,
110        if self.reading.get() {
111            // Set readAgain to true.
112            self.read_again.set(true);
113            // Return a promise resolved with undefined.
114            rooted!(in(*cx) let mut rval = UndefinedValue());
115            return Promise::new_resolved(&self.stream.global(), cx, rval.handle(), can_gc);
116        }
117
118        // Set reading to true.
119        self.reading.set(true);
120
121        // Let readRequest be a read request with the following items:
122        let tee_read_request = DefaultTeeReadRequest::new(
123            &self.stream,
124            &self.branch_1.get().expect("Branch 1 should be set."),
125            &self.branch_2.get().expect("Branch 2 should be set."),
126            self.reading.clone(),
127            self.read_again.clone(),
128            self.canceled_1.clone(),
129            self.canceled_2.clone(),
130            self.clone_for_branch_2.clone(),
131            self.cancel_promise.clone(),
132            self,
133            can_gc,
134        );
135
136        // Rooting: the tee read request is rooted above.
137        let read_request = ReadRequest::DefaultTee {
138            tee_read_request: Dom::from_ref(&tee_read_request),
139        };
140
141        // Perform ! ReadableStreamDefaultReaderRead(reader, readRequest).
142        self.reader.read(cx, &read_request, can_gc);
143
144        // Return a promise resolved with undefined.
145        rooted!(in(*cx) let mut rval = UndefinedValue());
146        Promise::new_resolved(&self.stream.global(), cx, rval.handle(), can_gc)
147    }
148
149    /// <https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee>
150    /// Let cancel1Algorithm be the following steps, taking a reason argument
151    /// and
152    /// Let cancel2Algorithm be the following steps, taking a reason argument
153    pub(crate) fn cancel_algorithm(
154        &self,
155        cx: SafeJSContext,
156        global: &GlobalScope,
157        reason: SafeHandleValue,
158        can_gc: CanGc,
159    ) -> Option<Result<Rc<Promise>, Error>> {
160        match self.tee_cancel_algorithm {
161            DefaultTeeCancelAlgorithm::Cancel1Algorithm => {
162                // Set canceled_1 to true.
163                self.canceled_1.set(true);
164
165                // Set reason_1 to reason.
166                self.reason_1.set(reason.get());
167
168                // If canceled_2 is true,
169                if self.canceled_2.get() {
170                    self.resolve_cancel_promise(cx, global, can_gc);
171                }
172                // Return cancelPromise.
173                Some(Ok(self.cancel_promise.clone()))
174            },
175            DefaultTeeCancelAlgorithm::Cancel2Algorithm => {
176                // Set canceled_2 to true.
177                self.canceled_2.set(true);
178
179                // Set reason_2 to reason.
180                self.reason_2.set(reason.get());
181
182                // If canceled_1 is true,
183                if self.canceled_1.get() {
184                    self.resolve_cancel_promise(cx, global, can_gc);
185                }
186                // Return cancelPromise.
187                Some(Ok(self.cancel_promise.clone()))
188            },
189        }
190    }
191
192    #[expect(unsafe_code)]
193    fn resolve_cancel_promise(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) {
194        // Let compositeReason be ! CreateArrayFromList(« reason_1, reason_2 »).
195        rooted_vec!(let mut reasons_values);
196        reasons_values.push(self.reason_1.get());
197        reasons_values.push(self.reason_2.get());
198
199        let reasons_values_array = HandleValueArray::from(&reasons_values);
200        rooted!(in(*cx) let reasons = unsafe { NewArrayObject(*cx, &reasons_values_array) });
201        rooted!(in(*cx) let reasons_value = ObjectValue(reasons.get()));
202
203        // Let cancelResult be ! ReadableStreamCancel(stream, compositeReason).
204        let cancel_result = self
205            .stream
206            .cancel(cx, global, reasons_value.handle(), can_gc);
207
208        // Resolve cancelPromise with cancelResult.
209        self.cancel_promise.resolve_native(&cancel_result, can_gc);
210    }
211}