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};
12
13use crate::DomTypes;
14use crate::dom::bindings::codegen::Bindings::TextDecoderBinding;
15use crate::dom::bindings::codegen::Bindings::TextDecoderStreamBinding::TextDecoderStreamMethods;
16use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
17use crate::dom::bindings::error::{Error, Fallible};
18use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
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};
25use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
26
27#[expect(unsafe_code)]
29pub(crate) fn decode_and_enqueue_a_chunk(
30 cx: &mut js::context::JSContext,
31 global: &GlobalScope,
32 chunk: SafeHandleValue,
33 decoder: &TextDecoderCommon,
34 controller: &TransformStreamDefaultController,
35) -> Fallible<()> {
36 let conversion_result = unsafe {
38 ArrayBufferViewOrArrayBuffer::from_jsval(cx.raw_cx(), chunk, ()).map_err(|_| {
39 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
40 })?
41 };
42 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
43 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
44 })?;
45
46 let output_chunk = decoder.decode(Some(buffer_source), false)?;
57
58 if output_chunk.is_empty() {
61 return Ok(());
62 }
63 rooted!(&in(cx) let mut rval = UndefinedValue());
64 unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
65 controller.enqueue(cx, global, rval.handle())
66}
67
68#[expect(unsafe_code)]
70pub(crate) fn flush_and_enqueue(
71 cx: &mut js::context::JSContext,
72 global: &GlobalScope,
73 decoder: &TextDecoderCommon,
74 controller: &TransformStreamDefaultController,
75) -> Fallible<()> {
76 let output_chunk = decoder.decode(None, true)?;
87
88 if output_chunk.is_empty() {
91 return Ok(());
92 }
93 rooted!(&in(cx) let mut rval = UndefinedValue());
94 unsafe { output_chunk.to_jsval(cx.raw_cx(), rval.handle_mut()) };
95 controller.enqueue(cx, global, rval.handle())
96}
97
98#[dom_struct]
100pub(crate) struct TextDecoderStream {
101 reflector_: Reflector,
102
103 #[conditional_malloc_size_of]
105 decoder: Rc<TextDecoderCommon>,
106
107 transform: Dom<TransformStream>,
109}
110
111#[expect(non_snake_case)]
112impl TextDecoderStream {
113 fn new_inherited(
114 decoder: Rc<TextDecoderCommon>,
115 transform: &TransformStream,
116 ) -> TextDecoderStream {
117 TextDecoderStream {
118 reflector_: Reflector::new(),
119 decoder,
120 transform: Dom::from_ref(transform),
121 }
122 }
123
124 fn new_with_proto(
125 cx: SafeJSContext,
126 global: &GlobalScope,
127 proto: Option<SafeHandleObject>,
128 encoding: &'static Encoding,
129 fatal: bool,
130 ignoreBOM: bool,
131 can_gc: CanGc,
132 ) -> Fallible<DomRoot<Self>> {
133 let decoder = Rc::new(TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM));
134 let transformer_type = TransformerType::Decoder(decoder.clone());
135
136 let transform_stream = TransformStream::new_with_proto(global, None, can_gc);
137 transform_stream.set_up(cx, global, transformer_type, can_gc)?;
138
139 Ok(reflect_dom_object_with_proto(
140 Box::new(TextDecoderStream::new_inherited(decoder, &transform_stream)),
141 global,
142 proto,
143 can_gc,
144 ))
145 }
146}
147
148impl TextDecoderStreamMethods<crate::DomTypeHolder> for TextDecoderStream {
149 fn Constructor(
151 global: &GlobalScope,
152 proto: Option<SafeHandleObject>,
153 can_gc: CanGc,
154 label: DOMString,
155 options: &TextDecoderBinding::TextDecoderOptions,
156 ) -> Fallible<DomRoot<TextDecoderStream>> {
157 let encoding = match Encoding::for_label_no_replacement(&label.as_bytes()) {
158 Some(enc) => enc,
159 None => {
160 return Err(Error::Range(
161 c"The given encoding is not supported".to_owned(),
162 ));
163 },
164 };
165
166 Self::new_with_proto(
167 GlobalScope::get_cx(),
168 global,
169 proto,
170 encoding,
171 options.fatal,
172 options.ignoreBOM,
173 can_gc,
174 )
175 }
176
177 fn Encoding(&self) -> DOMString {
179 DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
180 }
181
182 fn Fatal(&self) -> bool {
184 self.decoder.fatal()
185 }
186
187 fn IgnoreBOM(&self) -> bool {
189 self.decoder.ignore_bom()
190 }
191
192 fn Readable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::ReadableStream> {
194 self.transform.get_readable()
195 }
196
197 fn Writable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::WritableStream> {
199 self.transform.get_writable()
200 }
201}