use std::cell::Cell;
use std::collections::VecDeque;
use std::mem;
use std::rc::Rc;
use dom_struct::dom_struct;
use js::gc::CustomAutoRooterGuard;
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue};
use js::realm::CurrentRealm;
use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
use js::typedarray::{ArrayBufferView, ArrayBufferViewU8};
use script_bindings::root::Dom;
use super::byteteereadintorequest::ByteTeeReadIntoRequest;
use super::readablebytestreamcontroller::ReadableByteStreamController;
use super::readablestreamgenericreader::ReadableStreamGenericReader;
use crate::dom::bindings::buffer_source::{BufferSource, HeapBufferSource};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ReadableStreamBYOBReaderBinding::{
ReadableStreamBYOBReaderMethods, ReadableStreamBYOBReaderReadOptions,
};
use crate::dom::bindings::codegen::Bindings::ReadableStreamDefaultReaderBinding::ReadableStreamReadResult;
use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
use crate::dom::bindings::reflector::{
DomGlobal, Reflector, reflect_dom_object, reflect_dom_object_with_proto,
};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::stream::readablestream::ReadableStream;
use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub enum ReadIntoRequest {
Read(#[conditional_malloc_size_of] Rc<Promise>),
ByteTee {
byte_tee_read_into_request: Dom<ByteTeeReadIntoRequest>,
},
}
impl ReadIntoRequest {
pub fn chunk_steps(&self, chunk: RootedTraceableBox<Heap<JSVal>>, can_gc: CanGc) {
match self {
ReadIntoRequest::Read(promise) => {
promise.resolve_native(
&ReadableStreamReadResult {
done: Some(false),
value: chunk,
},
can_gc,
);
},
ReadIntoRequest::ByteTee {
byte_tee_read_into_request,
} => {
byte_tee_read_into_request.enqueue_chunk_steps(
HeapBufferSource::<ArrayBufferViewU8>::new(BufferSource::ArrayBufferView(
RootedTraceableBox::from_box(Heap::boxed(chunk.get().to_object())),
)),
)
},
}
}
pub fn close_steps(&self, chunk: Option<RootedTraceableBox<Heap<JSVal>>>, can_gc: CanGc) {
match self {
ReadIntoRequest::Read(promise) => match chunk {
Some(chunk) => promise.resolve_native(
&ReadableStreamReadResult {
done: Some(true),
value: chunk,
},
can_gc,
),
None => {
let result = RootedTraceableBox::new(Heap::default());
result.set(UndefinedValue());
promise.resolve_native(
&ReadableStreamReadResult {
done: Some(true),
value: result,
},
can_gc,
);
},
},
ReadIntoRequest::ByteTee {
byte_tee_read_into_request,
} => match chunk {
Some(chunk) => byte_tee_read_into_request
.close_steps(
Some(HeapBufferSource::<ArrayBufferViewU8>::new(
BufferSource::ArrayBufferView(RootedTraceableBox::from_box(
Heap::boxed(chunk.get().to_object()),
)),
)),
can_gc,
)
.expect("close steps should not fail"),
None => byte_tee_read_into_request
.close_steps(None, can_gc)
.expect("close steps should not fail"),
},
}
}
pub(crate) fn error_steps(&self, e: SafeHandleValue, can_gc: CanGc) {
match self {
ReadIntoRequest::Read(promise) => {
promise.reject_native(&e, can_gc)
},
ReadIntoRequest::ByteTee {
byte_tee_read_into_request,
} => {
byte_tee_read_into_request.error_steps();
},
}
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct ByteTeeClosedPromiseRejectionHandler {
branch_1_controller: Dom<ReadableByteStreamController>,
branch_2_controller: Dom<ReadableByteStreamController>,
#[conditional_malloc_size_of]
canceled_1: Rc<Cell<bool>>,
#[conditional_malloc_size_of]
canceled_2: Rc<Cell<bool>>,
#[conditional_malloc_size_of]
cancel_promise: Rc<Promise>,
#[conditional_malloc_size_of]
reader_version: Rc<Cell<u64>>,
expected_version: u64,
}
impl Callback for ByteTeeClosedPromiseRejectionHandler {
fn callback(&self, cx: &mut CurrentRealm, v: SafeHandleValue) {
let can_gc = CanGc::from_cx(cx);
if self.reader_version.get() != self.expected_version {
return;
}
self.branch_1_controller.error(v, can_gc);
self.branch_2_controller.error(v, can_gc);
if !self.canceled_1.get() || !self.canceled_2.get() {
self.cancel_promise.resolve_native(&(), can_gc);
}
}
}
#[dom_struct]
pub(crate) struct ReadableStreamBYOBReader {
reflector_: Reflector,
stream: MutNullableDom<ReadableStream>,
read_into_requests: DomRefCell<VecDeque<ReadIntoRequest>>,
#[conditional_malloc_size_of]
closed_promise: DomRefCell<Rc<Promise>>,
}
impl ReadableStreamBYOBReader {
fn new_with_proto(
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
) -> DomRoot<ReadableStreamBYOBReader> {
reflect_dom_object_with_proto(
Box::new(ReadableStreamBYOBReader::new_inherited(global, can_gc)),
global,
proto,
can_gc,
)
}
fn new_inherited(global: &GlobalScope, can_gc: CanGc) -> ReadableStreamBYOBReader {
ReadableStreamBYOBReader {
reflector_: Reflector::new(),
stream: MutNullableDom::new(None),
read_into_requests: DomRefCell::new(Default::default()),
closed_promise: DomRefCell::new(Promise::new(global, can_gc)),
}
}
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<ReadableStreamBYOBReader> {
reflect_dom_object(
Box::new(Self::new_inherited(global, can_gc)),
global,
can_gc,
)
}
pub(crate) fn set_up(
&self,
stream: &ReadableStream,
global: &GlobalScope,
can_gc: CanGc,
) -> Fallible<()> {
if stream.is_locked() {
return Err(Error::Type(c"stream is locked".to_owned()));
}
if !stream.has_byte_controller() {
return Err(Error::Type(
c"stream controller is not a byte stream controller".to_owned(),
));
}
self.generic_initialize(global, stream, can_gc);
self.read_into_requests.borrow_mut().clear();
Ok(())
}
pub(crate) fn release(&self, can_gc: CanGc) -> Fallible<()> {
self.generic_release(can_gc)
.expect("Generic release failed");
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut error = UndefinedValue());
Error::Type(c"Reader is released".to_owned()).to_jsval(
cx,
&self.global(),
error.handle_mut(),
can_gc,
);
self.error_read_into_requests(error.handle(), can_gc);
Ok(())
}
pub(crate) fn error_read_into_requests(&self, e: SafeHandleValue, can_gc: CanGc) {
self.closed_promise.borrow().reject_native(&e, can_gc);
self.closed_promise.borrow().set_promise_is_handled();
let mut read_into_requests = self.take_read_into_requests();
for request in read_into_requests.drain(0..) {
request.error_steps(e, can_gc);
}
}
fn take_read_into_requests(&self) -> VecDeque<ReadIntoRequest> {
mem::take(&mut *self.read_into_requests.borrow_mut())
}
pub(crate) fn add_read_into_request(&self, read_request: &ReadIntoRequest) {
self.read_into_requests
.borrow_mut()
.push_back(read_request.clone());
}
pub(crate) fn cancel(&self, can_gc: CanGc) {
let mut read_into_requests = self.take_read_into_requests();
for request in read_into_requests.drain(0..) {
request.close_steps(None, can_gc);
}
}
pub(crate) fn close(&self, can_gc: CanGc) {
self.closed_promise.borrow().resolve_native(&(), can_gc);
}
pub(crate) fn read(
&self,
cx: SafeJSContext,
view: HeapBufferSource<ArrayBufferViewU8>,
min: u64,
read_into_request: &ReadIntoRequest,
can_gc: CanGc,
) {
assert!(self.stream.get().is_some());
let stream = self.stream.get().unwrap();
stream.set_is_disturbed(true);
if stream.is_errored() {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut error = UndefinedValue());
stream.get_stored_error(error.handle_mut());
read_into_request.error_steps(error.handle(), can_gc);
} else {
stream.perform_pull_into(cx, read_into_request, view, min, can_gc);
}
}
pub(crate) fn get_num_read_into_requests(&self) -> usize {
self.read_into_requests.borrow().len()
}
pub(crate) fn remove_read_into_request(&self) -> ReadIntoRequest {
self.read_into_requests
.borrow_mut()
.pop_front()
.expect("read into requests is empty")
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn byte_tee_append_native_handler_to_closed_promise(
&self,
branch_1: &ReadableStream,
branch_2: &ReadableStream,
canceled_1: Rc<Cell<bool>>,
canceled_2: Rc<Cell<bool>>,
cancel_promise: Rc<Promise>,
reader_version: Rc<Cell<u64>>,
expected_version: u64,
can_gc: CanGc,
) {
let branch_1_controller = branch_1.get_byte_controller();
let branch_2_controller = branch_2.get_byte_controller();
let global = self.global();
let handler = PromiseNativeHandler::new(
&global,
None,
Some(Box::new(ByteTeeClosedPromiseRejectionHandler {
branch_1_controller: Dom::from_ref(&branch_1_controller),
branch_2_controller: Dom::from_ref(&branch_2_controller),
canceled_1,
canceled_2,
cancel_promise,
reader_version,
expected_version,
})),
can_gc,
);
let realm = enter_realm(&*global);
let comp = InRealm::Entered(&realm);
self.closed_promise
.borrow()
.append_native_handler(&handler, comp, can_gc);
}
}
impl ReadableStreamBYOBReaderMethods<crate::DomTypeHolder> for ReadableStreamBYOBReader {
fn Constructor(
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
stream: &ReadableStream,
) -> Fallible<DomRoot<Self>> {
let reader = Self::new_with_proto(global, proto, can_gc);
Self::set_up(&reader, stream, global, can_gc)?;
Ok(reader)
}
fn Read(
&self,
cx: &mut js::context::JSContext,
view: CustomAutoRooterGuard<ArrayBufferView>,
options: &ReadableStreamBYOBReaderReadOptions,
) -> Rc<Promise> {
let view = HeapBufferSource::<ArrayBufferViewU8>::from_view(view);
let min = options.min;
let promise = Promise::new2(cx, &self.global());
if view.byte_length() == 0 {
promise.reject_error(
Error::Type(c"view byte length is 0".to_owned()),
CanGc::from_cx(cx),
);
return promise;
}
if view.viewed_buffer_array_byte_length(cx.into()) == 0 {
promise.reject_error(
Error::Type(c"viewed buffer byte length is 0".to_owned()),
CanGc::from_cx(cx),
);
return promise;
}
if view.is_detached_buffer(cx.into()) {
promise.reject_error(
Error::Type(c"view is detached".to_owned()),
CanGc::from_cx(cx),
);
return promise;
}
if min == 0 {
promise.reject_error(Error::Type(c"min is 0".to_owned()), CanGc::from_cx(cx));
return promise;
}
if view.has_typed_array_name() {
if min > (view.get_typed_array_length() as u64) {
promise.reject_error(
Error::Range(c"min is greater than array length".to_owned()),
CanGc::from_cx(cx),
);
return promise;
}
} else {
if min > (view.byte_length() as u64) {
promise.reject_error(
Error::Range(c"min is greater than byte length".to_owned()),
CanGc::from_cx(cx),
);
return promise;
}
}
if self.stream.get().is_none() {
promise.reject_error(
Error::Type(c"min is greater than byte length".to_owned()),
CanGc::from_cx(cx),
);
return promise;
}
let read_into_request = ReadIntoRequest::Read(promise.clone());
self.read(cx.into(), view, min, &read_into_request, CanGc::from_cx(cx));
promise
}
fn ReleaseLock(&self, can_gc: CanGc) -> Fallible<()> {
if self.stream.get().is_none() {
return Ok(());
}
self.release(can_gc)
}
fn Closed(&self) -> Rc<Promise> {
self.closed()
}
fn Cancel(&self, cx: &mut js::context::JSContext, reason: SafeHandleValue) -> Rc<Promise> {
self.generic_cancel(cx, &self.global(), reason)
}
}
impl ReadableStreamGenericReader for ReadableStreamBYOBReader {
fn get_closed_promise(&self) -> Rc<Promise> {
self.closed_promise.borrow().clone()
}
fn set_closed_promise(&self, promise: Rc<Promise>) {
*self.closed_promise.borrow_mut() = promise;
}
fn set_stream(&self, stream: Option<&ReadableStream>) {
self.stream.set(stream);
}
fn get_stream(&self) -> Option<DomRoot<ReadableStream>> {
self.stream.get()
}
fn as_byob_reader(&self) -> Option<&ReadableStreamBYOBReader> {
Some(self)
}
}