script/dom/encoding/
textdecoderstream.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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/// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
28#[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    // Step 1. Let bufferSource be the result of converting chunk to an AllowSharedBufferSource.
37    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    // Step 2. Push a copy of bufferSource to decoder’s I/O queue.
47    // Step 3. Let output be the I/O queue of scalar values « end-of-queue ».
48    // Step 4. While true:
49    // Step 4.1 Let item be the result of reading from decoder’s I/O queue.
50    // Step 4.2 If item is end-of-queue:
51    // Step 4.2.1 Let outputChunk be the result of running serialize I/O queue with decoder and output.
52    // Step 4.2.3 Return.
53    // Step 4.3 Let result be the result of processing an item with item, decoder’s decoder,
54    //      decoder’s I/O queue, output, and decoder’s error mode.
55    // Step 4.4 If result is error, then throw a TypeError.
56    let output_chunk = decoder.decode(Some(buffer_source), false)?;
57
58    // Step 4.2.2 If outputChunk is not the empty string, then enqueue
59    //      outputChunk in decoder’s transform.
60    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/// <https://encoding.spec.whatwg.org/#flush-and-enqueue>
69#[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    // Step 1. Let output be the I/O queue of scalar values « end-of-queue ».
77    // Step 2. While true:
78    // Step 2.1 Let item be the result of reading from decoder’s I/O queue.
79    // Step 2.2 Let result be the result of processing an item with item,
80    //      decoder’s decoder, decoder’s I/O queue, output, and decoder’s error mode.
81    // Step 2.3 If result is finished:
82    // Step 2.3.1 Let outputChunk be the result of running serialize I/O queue
83    //      with decoder and output.
84    // Step 2.3.3 Return.
85    // Step 2.3.4 Otherwise, if result is error, throw a TypeError.
86    let output_chunk = decoder.decode(None, true)?;
87
88    // Step 2.3.2 If outputChunk is not the empty string, then enqueue
89    //      outputChunk in decoder’s transform.
90    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/// <https://encoding.spec.whatwg.org/#textdecoderstream>
99#[dom_struct]
100pub(crate) struct TextDecoderStream {
101    reflector_: Reflector,
102
103    /// <https://encoding.spec.whatwg.org/#textdecodercommon>
104    #[conditional_malloc_size_of]
105    decoder: Rc<TextDecoderCommon>,
106
107    /// <https://streams.spec.whatwg.org/#generictransformstream>
108    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    /// <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
150    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    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-encoding>
178    fn Encoding(&self) -> DOMString {
179        DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
180    }
181
182    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-fatal>
183    fn Fatal(&self) -> bool {
184        self.decoder.fatal()
185    }
186
187    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom>
188    fn IgnoreBOM(&self) -> bool {
189        self.decoder.ignore_bom()
190    }
191
192    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
193    fn Readable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::ReadableStream> {
194        self.transform.get_readable()
195    }
196
197    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
198    fn Writable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::WritableStream> {
199        self.transform.get_writable()
200    }
201}