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