Skip to main content

script/dom/stream/
underlyingsourcecontainer.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::ptr;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::jsapi::{Heap, IsPromiseObject, JSObject};
11use js::jsval::{JSVal, UndefinedValue};
12use js::rust::{Handle as SafeHandle, HandleObject, HandleValue as SafeHandleValue, IntoHandle};
13use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
14
15use super::byteteeunderlyingsource::ByteTeeUnderlyingSource;
16use crate::dom::bindings::callback::ExceptionHandling;
17use crate::dom::bindings::codegen::Bindings::UnderlyingSourceBinding::UnderlyingSource as JsUnderlyingSource;
18use crate::dom::bindings::codegen::UnionTypes::ReadableStreamDefaultControllerOrReadableByteStreamController as Controller;
19use crate::dom::bindings::error::Error;
20use crate::dom::bindings::reflector::DomGlobal;
21use crate::dom::bindings::root::{Dom, DomRoot};
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::messageport::MessagePort;
24use crate::dom::promise::Promise;
25use crate::dom::stream::defaultteeunderlyingsource::DefaultTeeUnderlyingSource;
26use crate::dom::stream::transformstream::TransformStream;
27use crate::script_runtime::CanGc;
28
29/// A variation of [UnderlyingSourceType] used for storing state within UnderlyingContainer.
30/// All variants have identical meanings to [UnderlyingSourceType].
31#[derive(JSTraceable)]
32#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
33enum UnderlyingSource {
34    Memory(usize),
35    Blob(usize),
36    FetchResponse,
37    Js(JsUnderlyingSource, Heap<*mut JSObject>),
38    Tee(Dom<DefaultTeeUnderlyingSource>),
39    Transfer(Dom<MessagePort>),
40    Transform(Dom<TransformStream>, Rc<Promise>),
41    TeeByte(Dom<ByteTeeUnderlyingSource>),
42}
43
44impl UnderlyingSource {
45    /// Does the source have all data in memory?
46    fn in_memory(&self) -> bool {
47        matches!(self, UnderlyingSource::Memory(_))
48    }
49}
50
51impl From<UnderlyingSourceType<'_>> for UnderlyingSource {
52    fn from(underlying_source_type: UnderlyingSourceType<'_>) -> Self {
53        match underlying_source_type {
54            UnderlyingSourceType::Memory(size) => UnderlyingSource::Memory(size),
55            UnderlyingSourceType::Blob(size) => UnderlyingSource::Blob(size),
56            UnderlyingSourceType::FetchResponse => UnderlyingSource::FetchResponse,
57            UnderlyingSourceType::Js(source) => UnderlyingSource::Js(source, Heap::default()),
58            UnderlyingSourceType::Tee(source) => UnderlyingSource::Tee(Dom::from_ref(source)),
59            UnderlyingSourceType::Transfer(port) => UnderlyingSource::Transfer(Dom::from_ref(port)),
60            UnderlyingSourceType::Transform(stream, promise) => {
61                UnderlyingSource::Transform(Dom::from_ref(stream), promise)
62            },
63            UnderlyingSourceType::TeeByte(source) => {
64                UnderlyingSource::TeeByte(Dom::from_ref(source))
65            },
66        }
67    }
68}
69
70/// <https://streams.spec.whatwg.org/#underlying-source-api>
71/// The `Js` variant corresponds to
72/// the JavaScript object representing the underlying source.
73/// The other variants are native sources in Rust.
74pub(crate) enum UnderlyingSourceType<'a> {
75    /// Facilitate partial integration with sources
76    /// that are currently read into memory.
77    Memory(usize),
78    /// A blob as underlying source, with a known total size.
79    Blob(usize),
80    /// A fetch response as underlying source.
81    FetchResponse,
82    /// A struct representing a JS object as underlying source,
83    /// and the actual JS object for use as `thisArg` in callbacks.
84    Js(JsUnderlyingSource),
85    /// Tee
86    Tee(&'a DefaultTeeUnderlyingSource),
87    /// Transfer, with the port used in some of the algorithms.
88    Transfer(&'a MessagePort),
89    /// A struct representing a JS object as underlying source,
90    /// and the actual JS object for use as `thisArg` in callbacks.
91    /// This is used for the `TransformStream` API.
92    Transform(&'a TransformStream, Rc<Promise>),
93    /// Tee Byte
94    TeeByte(&'a ByteTeeUnderlyingSource),
95}
96
97impl UnderlyingSourceType<'_> {
98    /// Is the source backed by a Rust native source?
99    pub(crate) fn is_native(&self) -> bool {
100        matches!(
101            self,
102            UnderlyingSourceType::Memory(_) |
103                UnderlyingSourceType::Blob(_) |
104                UnderlyingSourceType::FetchResponse |
105                UnderlyingSourceType::Transfer(_)
106        )
107    }
108}
109
110/// Wrapper around the underlying source.
111#[dom_struct]
112pub(crate) struct UnderlyingSourceContainer {
113    reflector_: Reflector,
114    #[ignore_malloc_size_of = "JsUnderlyingSource implemented in SM."]
115    underlying_source_type: UnderlyingSource,
116}
117
118impl UnderlyingSourceContainer {
119    fn new_inherited(underlying_source_type: UnderlyingSourceType) -> UnderlyingSourceContainer {
120        UnderlyingSourceContainer {
121            reflector_: Reflector::new(),
122            underlying_source_type: underlying_source_type.into(),
123        }
124    }
125
126    pub(crate) fn new(
127        global: &GlobalScope,
128        underlying_source_type: UnderlyingSourceType,
129        can_gc: CanGc,
130    ) -> DomRoot<UnderlyingSourceContainer> {
131        // TODO: setting the underlying source dict as the prototype of the
132        // `UnderlyingSourceContainer`, as it is later used as the "this" in Call_.
133        // Is this a good idea?
134        reflect_dom_object_with_proto(
135            Box::new(UnderlyingSourceContainer::new_inherited(
136                underlying_source_type,
137            )),
138            global,
139            None,
140            can_gc,
141        )
142    }
143
144    /// Setting the JS object after the heap has settled down.
145    pub(crate) fn set_underlying_source_this_object(&self, object: HandleObject) {
146        if let UnderlyingSource::Js(_source, this_obj) = &self.underlying_source_type {
147            this_obj.set(*object);
148        }
149    }
150
151    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-cancel>
152    #[expect(unsafe_code)]
153    pub(crate) fn call_cancel_algorithm(
154        &self,
155        cx: &mut JSContext,
156        global: &GlobalScope,
157        reason: SafeHandleValue,
158    ) -> Option<Result<Rc<Promise>, Error>> {
159        match &self.underlying_source_type {
160            UnderlyingSource::Js(source, this_obj) => {
161                if let Some(algo) = &source.cancel {
162                    let result = unsafe {
163                        algo.Call_(
164                            cx,
165                            &SafeHandle::from_raw(this_obj.handle()),
166                            Some(reason),
167                            ExceptionHandling::Rethrow,
168                        )
169                    };
170                    return Some(result);
171                }
172                None
173            },
174            UnderlyingSource::Tee(tee_underlying_source) => {
175                // Call the cancel algorithm for the appropriate branch.
176                tee_underlying_source.cancel_algorithm(cx, global, reason)
177            },
178            UnderlyingSource::Transform(stream, _) => {
179                // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason).
180                Some(stream.transform_stream_default_source_cancel(cx, global, reason))
181            },
182            UnderlyingSource::Transfer(port) => {
183                // Let cancelAlgorithm be the following steps, taking a reason argument:
184                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
185
186                // Let result be PackAndPostMessageHandlingError(port, "error", reason).
187                let result = port.pack_and_post_message_handling_error(cx, "error", reason);
188
189                // Disentangle port.
190                self.global().disentangle_port(cx, port);
191
192                let promise = Promise::new2(cx, &self.global());
193
194                // If result is an abrupt completion,
195                if let Err(error) = result {
196                    // Return a promise rejected with result.[[Value]].
197                    promise.reject_error(error, CanGc::from_cx(cx));
198                } else {
199                    // Otherwise, return a promise resolved with undefined.
200                    promise.resolve_native(&(), CanGc::from_cx(cx));
201                }
202                Some(Ok(promise))
203            },
204            UnderlyingSource::TeeByte(tee_underlyin_source) => {
205                // Call the cancel algorithm for the appropriate branch.
206                tee_underlyin_source.cancel_algorithm(cx, reason)
207            },
208            _ => None,
209        }
210    }
211
212    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-pull>
213    #[expect(unsafe_code)]
214    pub(crate) fn call_pull_algorithm(
215        &self,
216        cx: &mut JSContext,
217        controller: Controller,
218    ) -> Option<Result<Rc<Promise>, Error>> {
219        match &self.underlying_source_type {
220            UnderlyingSource::Js(source, this_obj) => {
221                if let Some(algo) = &source.pull {
222                    let result = unsafe {
223                        algo.Call_(
224                            cx,
225                            &SafeHandle::from_raw(this_obj.handle()),
226                            controller,
227                            ExceptionHandling::Rethrow,
228                        )
229                    };
230                    return Some(result);
231                }
232                None
233            },
234            UnderlyingSource::Tee(tee_underlying_source) => {
235                // Call the pull algorithm for the appropriate branch.
236                Some(Ok(tee_underlying_source.pull_algorithm(cx)))
237            },
238            UnderlyingSource::Transfer(port) => {
239                // Let pullAlgorithm be the following steps:
240                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
241
242                // Perform ! PackAndPostMessage(port, "pull", undefined).
243                rooted!(&in(cx) let mut value = UndefinedValue());
244                port.pack_and_post_message(cx, "pull", value.handle())
245                    .expect("Sending pull should not fail.");
246
247                // Return a promise resolved with undefined.
248                let promise =
249                    Promise::new_resolved(&self.global(), cx.into(), (), CanGc::from_cx(cx));
250                Some(Ok(promise))
251            },
252            UnderlyingSource::TeeByte(tee_underlyin_source) => {
253                // Call the pull algorithm for the appropriate branch.
254                Some(Ok(tee_underlyin_source.pull_algorithm(cx, None)))
255            },
256            // Note: other source type have no pull steps for now.
257            UnderlyingSource::Transform(stream, _) => {
258                // Return ! TransformStreamDefaultSourcePullAlgorithm(stream).
259                Some(
260                    stream.transform_stream_default_source_pull(&self.global(), CanGc::from_cx(cx)),
261                )
262            },
263            _ => None,
264        }
265    }
266
267    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-start>
268    ///
269    /// Note: The algorithm can return any value, including a promise,
270    /// we always transform the result into a promise for convenience,
271    /// and it is also how to spec deals with the situation.
272    /// see "Let startPromise be a promise resolved with startResult."
273    /// at <https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller>
274    #[expect(unsafe_code)]
275    pub(crate) fn call_start_algorithm(
276        &self,
277        cx: &mut JSContext,
278        controller: Controller,
279    ) -> Option<Result<Rc<Promise>, Error>> {
280        match &self.underlying_source_type {
281            UnderlyingSource::Js(source, this_obj) => {
282                if let Some(start) = &source.start {
283                    rooted!(&in(cx) let mut result_object = ptr::null_mut::<JSObject>());
284                    rooted!(&in(cx) let mut result: JSVal);
285                    unsafe {
286                        if let Err(error) = start.Call_(
287                            cx,
288                            &SafeHandle::from_raw(this_obj.handle()),
289                            controller,
290                            result.handle_mut(),
291                            ExceptionHandling::Rethrow,
292                        ) {
293                            return Some(Err(error));
294                        }
295                    }
296                    let is_promise = unsafe {
297                        if result.is_object() {
298                            result_object.set(result.to_object());
299                            IsPromiseObject(result_object.handle().into_handle())
300                        } else {
301                            false
302                        }
303                    };
304                    let promise = if is_promise {
305                        Promise::new_with_js_promise(result_object.handle(), cx.into())
306                    } else {
307                        Promise::new_resolved(
308                            &self.global(),
309                            cx.into(),
310                            result.get(),
311                            CanGc::from_cx(cx),
312                        )
313                    };
314                    return Some(Ok(promise));
315                }
316                None
317            },
318            UnderlyingSource::Tee(_) => {
319                // Let startAlgorithm be an algorithm that returns undefined.
320                None
321            },
322            UnderlyingSource::Transfer(_) => {
323                // Let startAlgorithm be an algorithm that returns undefined.
324                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
325                None
326            },
327            UnderlyingSource::Transform(_, start_promise) => {
328                // Let startAlgorithm be an algorithm that returns startPromise.
329                Some(Ok(start_promise.clone()))
330            },
331            _ => None,
332        }
333    }
334
335    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-autoallocatechunksize>
336    pub(crate) fn auto_allocate_chunk_size(&self) -> Option<u64> {
337        match &self.underlying_source_type {
338            UnderlyingSource::Js(source, _) => source.autoAllocateChunkSize,
339            _ => None,
340        }
341    }
342
343    /// Does the source have all data in memory?
344    pub(crate) fn in_memory(&self) -> bool {
345        self.underlying_source_type.in_memory()
346    }
347}