Skip to main content

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