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