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