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;
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: &mut js::context::JSContext,
122        global: &GlobalScope,
123        reason: SafeHandleValue,
124    ) -> Option<Result<Rc<Promise>, Error>> {
125        match &self.underlying_source_type {
126            UnderlyingSourceType::Js(source, this_obj) => {
127                if let Some(algo) = &source.cancel {
128                    let result = unsafe {
129                        algo.Call_(
130                            &SafeHandle::from_raw(this_obj.handle()),
131                            Some(reason),
132                            ExceptionHandling::Rethrow,
133                            CanGc::from_cx(cx),
134                        )
135                    };
136                    return Some(result);
137                }
138                None
139            },
140            UnderlyingSourceType::Tee(tee_underlying_source) => {
141                // Call the cancel algorithm for the appropriate branch.
142                tee_underlying_source.cancel_algorithm(cx, global, reason)
143            },
144            UnderlyingSourceType::Transform(stream, _) => {
145                // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason).
146                Some(stream.transform_stream_default_source_cancel(
147                    cx.into(),
148                    global,
149                    reason,
150                    CanGc::from_cx(cx),
151                ))
152            },
153            UnderlyingSourceType::Transfer(port) => {
154                // Let cancelAlgorithm be the following steps, taking a reason argument:
155                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
156
157                // Let result be PackAndPostMessageHandlingError(port, "error", reason).
158                let result =
159                    port.pack_and_post_message_handling_error("error", reason, CanGc::from_cx(cx));
160
161                // Disentangle port.
162                self.global().disentangle_port(cx, port);
163
164                let promise = Promise::new2(cx, &self.global());
165
166                // If result is an abrupt completion,
167                if let Err(error) = result {
168                    // Return a promise rejected with result.[[Value]].
169                    promise.reject_error(error, CanGc::from_cx(cx));
170                } else {
171                    // Otherwise, return a promise resolved with undefined.
172                    promise.resolve_native(&(), CanGc::from_cx(cx));
173                }
174                Some(Ok(promise))
175            },
176            UnderlyingSourceType::TeeByte(tee_underlyin_source) => {
177                // Call the cancel algorithm for the appropriate branch.
178                tee_underlyin_source.cancel_algorithm(cx, reason)
179            },
180            _ => None,
181        }
182    }
183
184    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-pull>
185    #[expect(unsafe_code)]
186    pub(crate) fn call_pull_algorithm(
187        &self,
188        controller: Controller,
189        can_gc: CanGc,
190    ) -> Option<Result<Rc<Promise>, Error>> {
191        match &self.underlying_source_type {
192            UnderlyingSourceType::Js(source, this_obj) => {
193                if let Some(algo) = &source.pull {
194                    let result = unsafe {
195                        algo.Call_(
196                            &SafeHandle::from_raw(this_obj.handle()),
197                            controller,
198                            ExceptionHandling::Rethrow,
199                            can_gc,
200                        )
201                    };
202                    return Some(result);
203                }
204                None
205            },
206            UnderlyingSourceType::Tee(tee_underlying_source) => {
207                // Call the pull algorithm for the appropriate branch.
208                Some(Ok(tee_underlying_source.pull_algorithm(can_gc)))
209            },
210            UnderlyingSourceType::Transfer(port) => {
211                // Let pullAlgorithm be the following steps:
212                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
213
214                let cx = GlobalScope::get_cx();
215
216                // Perform ! PackAndPostMessage(port, "pull", undefined).
217                rooted!(in(*cx) let mut value = UndefinedValue());
218                port.pack_and_post_message("pull", value.handle(), can_gc)
219                    .expect("Sending pull should not fail.");
220
221                // Return a promise resolved with undefined.
222                let promise = Promise::new(&self.global(), can_gc);
223                promise.resolve_native(&(), can_gc);
224                Some(Ok(promise))
225            },
226            UnderlyingSourceType::TeeByte(tee_underlyin_source) => {
227                // Call the pull algorithm for the appropriate branch.
228                Some(Ok(tee_underlyin_source.pull_algorithm(None, can_gc)))
229            },
230            // Note: other source type have no pull steps for now.
231            UnderlyingSourceType::Transform(stream, _) => {
232                // Return ! TransformStreamDefaultSourcePullAlgorithm(stream).
233                Some(stream.transform_stream_default_source_pull(&self.global(), can_gc))
234            },
235            _ => None,
236        }
237    }
238
239    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-start>
240    ///
241    /// Note: The algorithm can return any value, including a promise,
242    /// we always transform the result into a promise for convenience,
243    /// and it is also how to spec deals with the situation.
244    /// see "Let startPromise be a promise resolved with startResult."
245    /// at <https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller>
246    #[expect(unsafe_code)]
247    pub(crate) fn call_start_algorithm(
248        &self,
249        cx: &mut js::context::JSContext,
250        controller: Controller,
251    ) -> Option<Result<Rc<Promise>, Error>> {
252        match &self.underlying_source_type {
253            UnderlyingSourceType::Js(source, this_obj) => {
254                if let Some(start) = &source.start {
255                    rooted!(&in(cx) let mut result_object = ptr::null_mut::<JSObject>());
256                    rooted!(&in(cx) let mut result: JSVal);
257                    unsafe {
258                        if let Err(error) = start.Call_(
259                            &SafeHandle::from_raw(this_obj.handle()),
260                            controller,
261                            result.handle_mut(),
262                            ExceptionHandling::Rethrow,
263                            CanGc::from_cx(cx),
264                        ) {
265                            return Some(Err(error));
266                        }
267                    }
268                    let is_promise = unsafe {
269                        if result.is_object() {
270                            result_object.set(result.to_object());
271                            IsPromiseObject(result_object.handle().into_handle())
272                        } else {
273                            false
274                        }
275                    };
276                    let promise = if is_promise {
277                        Promise::new_with_js_promise(result_object.handle(), cx.into())
278                    } else {
279                        Promise::new_resolved(
280                            &self.global(),
281                            cx.into(),
282                            result.get(),
283                            CanGc::from_cx(cx),
284                        )
285                    };
286                    return Some(Ok(promise));
287                }
288                None
289            },
290            UnderlyingSourceType::Tee(_) => {
291                // Let startAlgorithm be an algorithm that returns undefined.
292                None
293            },
294            UnderlyingSourceType::Transfer(_) => {
295                // Let startAlgorithm be an algorithm that returns undefined.
296                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
297                None
298            },
299            UnderlyingSourceType::Transform(_, start_promise) => {
300                // Let startAlgorithm be an algorithm that returns startPromise.
301                Some(Ok(start_promise.clone()))
302            },
303            _ => None,
304        }
305    }
306
307    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-autoallocatechunksize>
308    pub(crate) fn auto_allocate_chunk_size(&self) -> Option<u64> {
309        match &self.underlying_source_type {
310            UnderlyingSourceType::Js(source, _) => source.autoAllocateChunkSize,
311            _ => None,
312        }
313    }
314
315    /// Does the source have all data in memory?
316    pub(crate) fn in_memory(&self) -> bool {
317        self.underlying_source_type.in_memory()
318    }
319}