use std::rc::Rc;
use dom_struct::dom_struct;
use encoding_rs::Encoding;
use js::conversions::{FromJSValConvertible, ToJSValConvertible};
use js::jsval::UndefinedValue;
use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
use crate::DomTypes;
use crate::dom::bindings::codegen::Bindings::TextDecoderBinding;
use crate::dom::bindings::codegen::Bindings::TextDecoderStreamBinding::TextDecoderStreamMethods;
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::encoding::textdecodercommon::TextDecoderCommon;
use crate::dom::globalscope::GlobalScope;
use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
use crate::dom::types::{TransformStream, TransformStreamDefaultController};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
#[expect(unsafe_code)]
pub(crate) fn decode_and_enqueue_a_chunk(
cx: &mut js::context::JSContext,
global: &GlobalScope,
chunk: SafeHandleValue,
decoder: &TextDecoderCommon,
controller: &TransformStreamDefaultController,
) -> Fallible<()> {
let conversion_result = unsafe {
ArrayBufferViewOrArrayBuffer::from_jsval(cx.raw_cx(), chunk, ()).map_err(|_| {
Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
})?
};
let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
})?;
let output_chunk = decoder.decode(Some(buffer_source), false)?;
if output_chunk.is_empty() {
return Ok(());
}
rooted!(&in(cx) let mut rval = UndefinedValue());
unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
controller.enqueue(cx, global, rval.handle())
}
#[expect(unsafe_code)]
pub(crate) fn flush_and_enqueue(
cx: &mut js::context::JSContext,
global: &GlobalScope,
decoder: &TextDecoderCommon,
controller: &TransformStreamDefaultController,
) -> Fallible<()> {
let output_chunk = decoder.decode(None, true)?;
if output_chunk.is_empty() {
return Ok(());
}
rooted!(&in(cx) let mut rval = UndefinedValue());
unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
controller.enqueue(cx, global, rval.handle())
}
#[dom_struct]
pub(crate) struct TextDecoderStream {
reflector_: Reflector,
#[conditional_malloc_size_of]
decoder: Rc<TextDecoderCommon>,
transform: Dom<TransformStream>,
}
#[expect(non_snake_case)]
impl TextDecoderStream {
fn new_inherited(
decoder: Rc<TextDecoderCommon>,
transform: &TransformStream,
) -> TextDecoderStream {
TextDecoderStream {
reflector_: Reflector::new(),
decoder,
transform: Dom::from_ref(transform),
}
}
fn new_with_proto(
cx: SafeJSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
encoding: &'static Encoding,
fatal: bool,
ignoreBOM: bool,
can_gc: CanGc,
) -> Fallible<DomRoot<Self>> {
let decoder = Rc::new(TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM));
let transformer_type = TransformerType::Decoder(decoder.clone());
let transform_stream = TransformStream::new_with_proto(global, None, can_gc);
transform_stream.set_up(cx, global, transformer_type, can_gc)?;
Ok(reflect_dom_object_with_proto(
Box::new(TextDecoderStream::new_inherited(decoder, &transform_stream)),
global,
proto,
can_gc,
))
}
}
impl TextDecoderStreamMethods<crate::DomTypeHolder> for TextDecoderStream {
fn Constructor(
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
label: DOMString,
options: &TextDecoderBinding::TextDecoderOptions,
) -> Fallible<DomRoot<TextDecoderStream>> {
let encoding = match Encoding::for_label_no_replacement(&label.as_bytes()) {
Some(enc) => enc,
None => {
return Err(Error::Range(
c"The given encoding is not supported".to_owned(),
));
},
};
Self::new_with_proto(
GlobalScope::get_cx(),
global,
proto,
encoding,
options.fatal,
options.ignoreBOM,
can_gc,
)
}
fn Encoding(&self) -> DOMString {
DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
}
fn Fatal(&self) -> bool {
self.decoder.fatal()
}
fn IgnoreBOM(&self) -> bool {
self.decoder.ignore_bom()
}
fn Readable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::ReadableStream> {
self.transform.get_readable()
}
fn Writable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::WritableStream> {
self.transform.get_writable()
}
}