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