script/dom/encoding/
textdecoderstream.rs1use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use encoding_rs::Encoding;
9use js::conversions::{FromJSValConvertible, ToJSValConvertible};
10use js::jsval::UndefinedValue;
11use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
13
14use crate::DomTypes;
15use crate::dom::bindings::codegen::Bindings::TextDecoderBinding;
16use crate::dom::bindings::codegen::Bindings::TextDecoderStreamBinding::TextDecoderStreamMethods;
17use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
18use crate::dom::bindings::error::{Error, Fallible};
19use crate::dom::bindings::root::{Dom, DomRoot};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::encoding::textdecodercommon::TextDecoderCommon;
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
24use crate::dom::types::{TransformStream, TransformStreamDefaultController};
25
26#[expect(unsafe_code)]
28pub(crate) fn decode_and_enqueue_a_chunk(
29 cx: &mut js::context::JSContext,
30 global: &GlobalScope,
31 chunk: SafeHandleValue,
32 decoder: &TextDecoderCommon,
33 controller: &TransformStreamDefaultController,
34) -> Fallible<()> {
35 let conversion_result =
37 ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, ()).map_err(|_| {
38 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
39 })?;
40 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
41 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
42 })?;
43
44 let output_chunk = decoder.decode(Some(buffer_source), false)?;
55
56 if output_chunk.is_empty() {
59 return Ok(());
60 }
61 rooted!(&in(cx) let mut rval = UndefinedValue());
62 unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
63 controller.enqueue(cx, global, rval.handle())
64}
65
66#[expect(unsafe_code)]
68pub(crate) fn flush_and_enqueue(
69 cx: &mut js::context::JSContext,
70 global: &GlobalScope,
71 decoder: &TextDecoderCommon,
72 controller: &TransformStreamDefaultController,
73) -> Fallible<()> {
74 let output_chunk = decoder.decode(None, true)?;
85
86 if output_chunk.is_empty() {
89 return Ok(());
90 }
91 rooted!(&in(cx) let mut rval = UndefinedValue());
92 unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
93 controller.enqueue(cx, global, rval.handle())
94}
95
96#[dom_struct]
98pub(crate) struct TextDecoderStream {
99 reflector_: Reflector,
100
101 #[conditional_malloc_size_of]
103 decoder: Rc<TextDecoderCommon>,
104
105 transform: Dom<TransformStream>,
107}
108
109#[expect(non_snake_case)]
110impl TextDecoderStream {
111 fn new_inherited(
112 decoder: Rc<TextDecoderCommon>,
113 transform: &TransformStream,
114 ) -> TextDecoderStream {
115 TextDecoderStream {
116 reflector_: Reflector::new(),
117 decoder,
118 transform: Dom::from_ref(transform),
119 }
120 }
121
122 pub(crate) fn new_with_proto(
123 cx: &mut js::context::JSContext,
124 global: &GlobalScope,
125 proto: Option<SafeHandleObject>,
126 encoding: &'static Encoding,
127 fatal: bool,
128 ignoreBOM: bool,
129 ) -> Fallible<DomRoot<Self>> {
130 let decoder = Rc::new(TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM));
131 let transformer_type = TransformerType::Decoder(decoder.clone());
132
133 let transform_stream = TransformStream::new_with_proto(cx, global, None);
134 transform_stream.set_up(cx, global, transformer_type)?;
135
136 Ok(reflect_dom_object_with_proto_and_cx(
137 Box::new(TextDecoderStream::new_inherited(decoder, &transform_stream)),
138 global,
139 proto,
140 cx,
141 ))
142 }
143}
144
145impl TextDecoderStreamMethods<crate::DomTypeHolder> for TextDecoderStream {
146 fn Constructor(
148 cx: &mut js::context::JSContext,
149 global: &GlobalScope,
150 proto: Option<SafeHandleObject>,
151 label: DOMString,
152 options: &TextDecoderBinding::TextDecoderOptions,
153 ) -> Fallible<DomRoot<TextDecoderStream>> {
154 let encoding = match Encoding::for_label_no_replacement(&label.as_bytes()) {
155 Some(enc) => enc,
156 None => {
157 return Err(Error::Range(
158 c"The given encoding is not supported".to_owned(),
159 ));
160 },
161 };
162
163 Self::new_with_proto(
164 cx,
165 global,
166 proto,
167 encoding,
168 options.fatal,
169 options.ignoreBOM,
170 )
171 }
172
173 fn Encoding(&self) -> DOMString {
175 DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
176 }
177
178 fn Fatal(&self) -> bool {
180 self.decoder.fatal()
181 }
182
183 fn IgnoreBOM(&self) -> bool {
185 self.decoder.ignore_bom()
186 }
187
188 fn Readable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::ReadableStream> {
190 self.transform.get_readable()
191 }
192
193 fn Writable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::WritableStream> {
195 self.transform.get_writable()
196 }
197}