/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use std::ptr; use std::rc::Rc; use dom_struct::dom_struct; use js::jsapi::{Heap, IsPromiseObject, JSObject}; use js::jsval::JSVal; use js::rust::{Handle as SafeHandle, HandleObject, HandleValue as SafeHandleValue, IntoHandle}; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::codegen::Bindings::UnderlyingSourceBinding::UnderlyingSource as JsUnderlyingSource; use crate::dom::bindings::codegen::UnionTypes::ReadableStreamDefaultControllerOrReadableByteStreamController as Controller; use crate::dom::bindings::error::Error; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::defaultteeunderlyingsource::DefaultTeeUnderlyingSource; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; use crate::script_runtime::CanGc; /// /// The `Js` variant corresponds to /// the JavaScript object representing the underlying source. /// The other variants are native sources in Rust. #[derive(JSTraceable)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) enum UnderlyingSourceType { /// Facilitate partial integration with sources /// that are currently read into memory. Memory(usize), /// A blob as underlying source, with a known total size. Blob(usize), /// A fetch response as underlying source. FetchResponse, /// A struct representing a JS object as underlying source, /// and the actual JS object for use as `thisArg` in callbacks. Js(JsUnderlyingSource, Heap<*mut JSObject>), /// Tee Tee(Dom), } impl UnderlyingSourceType { /// Is the source backed by a Rust native source? pub(crate) fn is_native(&self) -> bool { matches!( self, UnderlyingSourceType::Memory(_) | UnderlyingSourceType::Blob(_) | UnderlyingSourceType::FetchResponse ) } /// Does the source have all data in memory? pub(crate) fn in_memory(&self) -> bool { matches!(self, UnderlyingSourceType::Memory(_)) } } /// Wrapper around the underlying source. #[dom_struct] pub(crate) struct UnderlyingSourceContainer { reflector_: Reflector, #[ignore_malloc_size_of = "JsUnderlyingSource implemented in SM."] underlying_source_type: UnderlyingSourceType, } impl UnderlyingSourceContainer { #[cfg_attr(crown, allow(crown::unrooted_must_root))] fn new_inherited(underlying_source_type: UnderlyingSourceType) -> UnderlyingSourceContainer { UnderlyingSourceContainer { reflector_: Reflector::new(), underlying_source_type, } } #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn new( global: &GlobalScope, underlying_source_type: UnderlyingSourceType, can_gc: CanGc, ) -> DomRoot { // TODO: setting the underlying source dict as the prototype of the // `UnderlyingSourceContainer`, as it is later used as the "this" in Call_. // Is this a good idea? reflect_dom_object_with_proto( Box::new(UnderlyingSourceContainer::new_inherited( underlying_source_type, )), global, None, can_gc, ) } /// Setting the JS object after the heap has settled down. pub(crate) fn set_underlying_source_this_object(&self, object: HandleObject) { if let UnderlyingSourceType::Js(_source, this_obj) = &self.underlying_source_type { this_obj.set(*object); } } /// #[allow(unsafe_code)] pub(crate) fn call_cancel_algorithm( &self, reason: SafeHandleValue, can_gc: CanGc, ) -> Option, Error>> { match &self.underlying_source_type { UnderlyingSourceType::Js(source, this_obj) => { if let Some(algo) = &source.cancel { let result = unsafe { algo.Call_( &SafeHandle::from_raw(this_obj.handle()), Some(reason), ExceptionHandling::Rethrow, can_gc, ) }; return Some(result); } None }, UnderlyingSourceType::Tee(tee_underlyin_source) => { // Call the cancel algorithm for the appropriate branch. tee_underlyin_source.cancel_algorithm(reason, can_gc) }, _ => None, } } /// #[allow(unsafe_code)] pub(crate) fn call_pull_algorithm( &self, controller: Controller, can_gc: CanGc, ) -> Option, Error>> { match &self.underlying_source_type { UnderlyingSourceType::Js(source, this_obj) => { if let Some(algo) = &source.pull { let result = unsafe { algo.Call_( &SafeHandle::from_raw(this_obj.handle()), controller, ExceptionHandling::Rethrow, can_gc, ) }; return Some(result); } None }, UnderlyingSourceType::Tee(tee_underlyin_source) => { // Call the pull algorithm for the appropriate branch. Some(Ok(tee_underlyin_source.pull_algorithm(can_gc))) }, // Note: other source type have no pull steps for now. _ => None, } } /// /// /// Note: The algorithm can return any value, including a promise, /// we always transform the result into a promise for convenience, /// and it is also how to spec deals with the situation. /// see "Let startPromise be a promise resolved with startResult." /// at #[allow(unsafe_code)] pub(crate) fn call_start_algorithm( &self, controller: Controller, can_gc: CanGc, ) -> Option, Error>> { match &self.underlying_source_type { UnderlyingSourceType::Js(source, this_obj) => { if let Some(start) = &source.start { let cx = GlobalScope::get_cx(); rooted!(in(*cx) let mut result_object = ptr::null_mut::()); rooted!(in(*cx) let mut result: JSVal); unsafe { if let Err(error) = start.Call_( &SafeHandle::from_raw(this_obj.handle()), controller, result.handle_mut(), ExceptionHandling::Rethrow, can_gc, ) { return Some(Err(error)); } } let is_promise = unsafe { if result.is_object() { result_object.set(result.to_object()); IsPromiseObject(result_object.handle().into_handle()) } else { false } }; let promise = if is_promise { let promise = Promise::new_with_js_promise(result_object.handle(), cx); promise } else { let promise = Promise::new(&self.global(), can_gc); promise.resolve_native(&result.get(), can_gc); promise }; return Some(Ok(promise)); } None }, UnderlyingSourceType::Tee(_) => { // Let startAlgorithm be an algorithm that returns undefined. None }, _ => None, } } /// pub(crate) fn auto_allocate_chunk_size(&self) -> Option { match &self.underlying_source_type { UnderlyingSourceType::Js(source, _) => source.autoAllocateChunkSize, _ => None, } } /// Does the source have all data in memory? pub(crate) fn in_memory(&self) -> bool { self.underlying_source_type.in_memory() } }