| /* 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, UndefinedValue}; |
| 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::messageport::MessagePort; |
| use crate::dom::promise::Promise; |
| use crate::dom::transformstream::TransformStream; |
| use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; |
| |
| /// <https://streams.spec.whatwg.org/#underlying-source-api> |
| /// 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<DefaultTeeUnderlyingSource>), |
| /// Transfer, with the port used in some of the algorithms. |
| Transfer(Dom<MessagePort>), |
| /// A struct representing a JS object as underlying source, |
| /// and the actual JS object for use as `thisArg` in callbacks. |
| /// This is used for the `TransformStream` API. |
| Transform(Dom<TransformStream>, Rc<Promise>), |
| } |
| |
| 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 | |
| UnderlyingSourceType::Transfer(_) |
| ) |
| } |
| |
| /// 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<UnderlyingSourceContainer> { |
| // 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); |
| } |
| } |
| |
| /// <https://streams.spec.whatwg.org/#dom-underlyingsource-cancel> |
| #[allow(unsafe_code)] |
| pub(crate) fn call_cancel_algorithm( |
| &self, |
| cx: SafeJSContext, |
| global: &GlobalScope, |
| reason: SafeHandleValue, |
| can_gc: CanGc, |
| ) -> Option<Result<Rc<Promise>, 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_underlying_source) => { |
| // Call the cancel algorithm for the appropriate branch. |
| tee_underlying_source.cancel_algorithm(cx, global, reason, can_gc) |
| }, |
| UnderlyingSourceType::Transform(stream, _) => { |
| // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason). |
| Some(stream.transform_stream_default_source_cancel(cx, global, reason, can_gc)) |
| }, |
| UnderlyingSourceType::Transfer(port) => { |
| // Let cancelAlgorithm be the following steps, taking a reason argument: |
| // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable |
| |
| // Let result be PackAndPostMessageHandlingError(port, "error", reason). |
| let result = port.pack_and_post_message_handling_error("error", reason, can_gc); |
| |
| // Disentangle port. |
| self.global().disentangle_port(port, can_gc); |
| |
| let promise = Promise::new(&self.global(), can_gc); |
| |
| // If result is an abrupt completion, |
| if let Err(error) = result { |
| // Return a promise rejected with result.[[Value]]. |
| promise.reject_error(error, can_gc); |
| } else { |
| // Otherwise, return a promise resolved with undefined. |
| promise.resolve_native(&(), can_gc); |
| } |
| Some(Ok(promise)) |
| }, |
| _ => None, |
| } |
| } |
| |
| /// <https://streams.spec.whatwg.org/#dom-underlyingsource-pull> |
| #[allow(unsafe_code)] |
| pub(crate) fn call_pull_algorithm( |
| &self, |
| controller: Controller, |
| _global: &GlobalScope, |
| can_gc: CanGc, |
| ) -> Option<Result<Rc<Promise>, 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_underlying_source) => { |
| // Call the pull algorithm for the appropriate branch. |
| Some(Ok(tee_underlying_source.pull_algorithm(can_gc))) |
| }, |
| UnderlyingSourceType::Transfer(port) => { |
| // Let pullAlgorithm be the following steps: |
| // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable |
| |
| let cx = GlobalScope::get_cx(); |
| |
| // Perform ! PackAndPostMessage(port, "pull", undefined). |
| rooted!(in(*cx) let mut value = UndefinedValue()); |
| port.pack_and_post_message("pull", value.handle(), can_gc) |
| .expect("Sending pull should not fail."); |
| |
| // Return a promise resolved with undefined. |
| let promise = Promise::new(&self.global(), can_gc); |
| promise.resolve_native(&(), can_gc); |
| Some(Ok(promise)) |
| }, |
| // Note: other source type have no pull steps for now. |
| UnderlyingSourceType::Transform(stream, _) => { |
| // Return ! TransformStreamDefaultSourcePullAlgorithm(stream). |
| Some(stream.transform_stream_default_source_pull(&self.global(), can_gc)) |
| }, |
| _ => None, |
| } |
| } |
| |
| /// <https://streams.spec.whatwg.org/#dom-underlyingsource-start> |
| /// |
| /// 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 <https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller> |
| #[allow(unsafe_code)] |
| pub(crate) fn call_start_algorithm( |
| &self, |
| controller: Controller, |
| can_gc: CanGc, |
| ) -> Option<Result<Rc<Promise>, 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::<JSObject>()); |
| 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 { |
| Promise::new_with_js_promise(result_object.handle(), cx) |
| } 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 |
| }, |
| UnderlyingSourceType::Transfer(_) => { |
| // Let startAlgorithm be an algorithm that returns undefined. |
| // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable |
| None |
| }, |
| UnderlyingSourceType::Transform(_, start_promise) => { |
| // Let startAlgorithm be an algorithm that returns startPromise. |
| Some(Ok(start_promise.clone())) |
| }, |
| _ => None, |
| } |
| } |
| |
| /// <https://streams.spec.whatwg.org/#dom-underlyingsource-autoallocatechunksize> |
| pub(crate) fn auto_allocate_chunk_size(&self) -> Option<u64> { |
| 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() |
| } |
| } |