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 dom_struct::dom_struct;
6use js::context::JSContext;
7use js::jsapi::{Heap, JSObject};
8use js::jsval::{JSVal, UndefinedValue};
9use js::rust::{HandleObject, HandleValue as SafeHandleValue};
10use script_bindings::reflector::{Reflector, reflect_dom_object};
11use script_bindings::root::rooted_heap_handle;
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;
19use crate::dom::bindings::root::{Dom, DomRoot};
20use crate::dom::globalscope::GlobalScope;
21use crate::dom::messageport::MessagePort;
22use crate::dom::promise::{Promise, RootedPromise, TracedPromise};
23use crate::dom::stream::defaultteeunderlyingsource::DefaultTeeUnderlyingSource;
24use crate::dom::stream::transformstream::TransformStream;
25
26/// A variation of [UnderlyingSourceType] used for storing state within UnderlyingContainer.
27/// All variants have identical meanings to [UnderlyingSourceType].
28#[derive(JSTraceable)]
29#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
30enum UnderlyingSource {
31    Memory(usize),
32    Blob(usize),
33    FetchResponse,
34    Js(JsUnderlyingSource, Heap<*mut JSObject>),
35    Tee(Dom<DefaultTeeUnderlyingSource>),
36    Transfer(Dom<MessagePort>),
37    Transform(Dom<TransformStream>, TracedPromise),
38    TeeByte(Dom<ByteTeeUnderlyingSource>),
39}
40
41impl UnderlyingSource {
42    /// Does the source have all data in memory?
43    fn in_memory(&self) -> bool {
44        matches!(self, UnderlyingSource::Memory(_))
45    }
46}
47
48impl From<UnderlyingSourceType<'_>> for UnderlyingSource {
49    fn from(underlying_source_type: UnderlyingSourceType<'_>) -> Self {
50        match underlying_source_type {
51            UnderlyingSourceType::Memory(size) => UnderlyingSource::Memory(size),
52            UnderlyingSourceType::Blob(size) => UnderlyingSource::Blob(size),
53            UnderlyingSourceType::FetchResponse => UnderlyingSource::FetchResponse,
54            UnderlyingSourceType::Js(source) => {
55                UnderlyingSource::Js(source.clone(), Heap::default())
56            },
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.to_traced())
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(&'a 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, &'a RootedPromise),
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(
134            cx,
135            Box::new(UnderlyingSourceContainer::new_inherited(
136                underlying_source_type,
137            )),
138            global,
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    pub(crate) fn call_cancel_algorithm(
151        &self,
152        cx: &mut JSContext,
153        global: &GlobalScope,
154        reason: SafeHandleValue,
155    ) -> Option<Result<RootedPromise, Error>> {
156        match &self.underlying_source_type {
157            UnderlyingSource::Js(source, this_obj) => {
158                if let Some(algo) = &source.cancel {
159                    let result = algo.Call_(
160                        cx,
161                        &rooted_heap_handle(self, |_| this_obj),
162                        Some(reason),
163                        ExceptionHandling::Rethrow,
164                    );
165                    return Some(result);
166                }
167                None
168            },
169            UnderlyingSource::Tee(tee_underlying_source) => {
170                // Call the cancel algorithm for the appropriate branch.
171                tee_underlying_source.cancel_algorithm(cx, global, reason)
172            },
173            UnderlyingSource::Transform(stream, _) => {
174                // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason).
175                Some(stream.transform_stream_default_source_cancel(cx, global, reason))
176            },
177            UnderlyingSource::Transfer(port) => {
178                // Let cancelAlgorithm be the following steps, taking a reason argument:
179                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
180
181                // Let result be PackAndPostMessageHandlingError(port, "error", reason).
182                let result = port.pack_and_post_message_handling_error(cx, "error", reason);
183
184                // Disentangle port.
185                self.global().disentangle_port(cx, port);
186
187                let promise = Promise::new(cx, &self.global());
188
189                // If result is an abrupt completion,
190                if let Err(error) = result {
191                    // Return a promise rejected with result.[[Value]].
192                    promise.reject_error(cx, error);
193                } else {
194                    // Otherwise, return a promise resolved with undefined.
195                    promise.resolve_native(cx, &());
196                }
197                Some(Ok(promise))
198            },
199            UnderlyingSource::TeeByte(tee_underlyin_source) => {
200                // Call the cancel algorithm for the appropriate branch.
201                tee_underlyin_source.cancel_algorithm(cx, reason)
202            },
203            _ => None,
204        }
205    }
206
207    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-pull>
208    pub(crate) fn call_pull_algorithm(
209        &self,
210        cx: &mut JSContext,
211        controller: Controller,
212    ) -> Option<Result<RootedPromise, Error>> {
213        match &self.underlying_source_type {
214            UnderlyingSource::Js(source, this_obj) => {
215                if let Some(algo) = &source.pull {
216                    let result = algo.Call_(
217                        cx,
218                        &rooted_heap_handle(self, |_| this_obj),
219                        controller,
220                        ExceptionHandling::Rethrow,
221                    );
222                    return Some(result);
223                }
224                None
225            },
226            UnderlyingSource::Tee(tee_underlying_source) => {
227                // Call the pull algorithm for the appropriate branch.
228                Some(Ok(tee_underlying_source.pull_algorithm(cx)))
229            },
230            UnderlyingSource::Transfer(port) => {
231                // Let pullAlgorithm be the following steps:
232                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
233
234                // Perform ! PackAndPostMessage(port, "pull", undefined).
235                rooted!(&in(cx) let mut value = UndefinedValue());
236                port.pack_and_post_message(cx, "pull", value.handle())
237                    .expect("Sending pull should not fail.");
238
239                // Return a promise resolved with undefined.
240                let promise = Promise::new_resolved(cx, &self.global(), ());
241                Some(Ok(promise))
242            },
243            UnderlyingSource::TeeByte(tee_underlyin_source) => {
244                // Call the pull algorithm for the appropriate branch.
245                Some(Ok(tee_underlyin_source.pull_algorithm(cx, None)))
246            },
247            // Note: other source type have no pull steps for now.
248            UnderlyingSource::Transform(stream, _) => {
249                // Return ! TransformStreamDefaultSourcePullAlgorithm(stream).
250                Some(stream.transform_stream_default_source_pull(cx, &self.global()))
251            },
252            _ => None,
253        }
254    }
255
256    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-start>
257    ///
258    /// Note: The algorithm can return any value, including a promise,
259    /// we always transform the result into a promise for convenience,
260    /// and it is also how to spec deals with the situation.
261    /// see "Let startPromise be a promise resolved with startResult."
262    /// at <https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller>
263    pub(crate) fn call_start_algorithm(
264        &self,
265        cx: &mut JSContext,
266        controller: Controller,
267    ) -> Option<Result<RootedPromise, Error>> {
268        match &self.underlying_source_type {
269            UnderlyingSource::Js(source, this_obj) => {
270                if let Some(start) = &source.start {
271                    rooted!(&in(cx) let mut result: JSVal);
272                    if let Err(error) = start.Call_(
273                        cx,
274                        &rooted_heap_handle(self, |_| this_obj),
275                        controller,
276                        result.handle_mut(),
277                        ExceptionHandling::Rethrow,
278                    ) {
279                        return Some(Err(error));
280                    }
281                    let promise =
282                        Promise::resolve_or_wrap_promise(cx, result.handle(), &self.global());
283                    return Some(Ok(promise));
284                }
285                None
286            },
287            UnderlyingSource::Tee(_) => {
288                // Let startAlgorithm be an algorithm that returns undefined.
289                None
290            },
291            UnderlyingSource::Transfer(_) => {
292                // Let startAlgorithm be an algorithm that returns undefined.
293                // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
294                None
295            },
296            UnderlyingSource::Transform(_, start_promise) => {
297                // Let startAlgorithm be an algorithm that returns startPromise.
298                Some(Ok(start_promise.root(cx)))
299            },
300            _ => None,
301        }
302    }
303
304    /// <https://streams.spec.whatwg.org/#dom-underlyingsource-autoallocatechunksize>
305    pub(crate) fn auto_allocate_chunk_size(&self) -> Option<u64> {
306        match &self.underlying_source_type {
307            UnderlyingSource::Js(source, _) => source.autoAllocateChunkSize,
308            _ => None,
309        }
310    }
311
312    /// Does the source have all data in memory?
313    pub(crate) fn in_memory(&self) -> bool {
314        self.underlying_source_type.in_memory()
315    }
316}