Skip to main content

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};
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
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/// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
27pub(crate) fn decode_and_enqueue_a_chunk(
28    cx: &mut js::context::JSContext,
29    global: &GlobalScope,
30    chunk: SafeHandleValue,
31    decoder: &TextDecoderCommon,
32    controller: &TransformStreamDefaultController,
33) -> Fallible<()> {
34    // Step 1. Let bufferSource be the result of converting chunk to an AllowSharedBufferSource.
35    let conversion_result =
36        ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, ()).map_err(|_| {
37            Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
38        })?;
39    let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
40        Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
41    })?;
42
43    // Step 2. Push a copy of bufferSource to decoder’s I/O queue.
44    // Step 3. Let output be the I/O queue of scalar values « end-of-queue ».
45    // Step 4. While true:
46    // Step 4.1 Let item be the result of reading from decoder’s I/O queue.
47    // Step 4.2 If item is end-of-queue:
48    // Step 4.2.1 Let outputChunk be the result of running serialize I/O queue with decoder and output.
49    // Step 4.2.3 Return.
50    // Step 4.3 Let result be the result of processing an item with item, decoder’s decoder,
51    //      decoder’s I/O queue, output, and decoder’s error mode.
52    // Step 4.4 If result is error, then throw a TypeError.
53    let output_chunk = decoder.decode(cx.no_gc(), Some(buffer_source), false)?;
54
55    // Step 4.2.2 If outputChunk is not the empty string, then enqueue
56    //      outputChunk in decoder’s transform.
57    if output_chunk.is_empty() {
58        return Ok(());
59    }
60    rooted!(&in(cx) let mut rval = UndefinedValue());
61    output_chunk.safe_to_jsval(cx, rval.handle_mut());
62    controller.enqueue(cx, global, rval.handle())
63}
64
65/// <https://encoding.spec.whatwg.org/#flush-and-enqueue>
66pub(crate) fn flush_and_enqueue(
67    cx: &mut js::context::JSContext,
68    global: &GlobalScope,
69    decoder: &TextDecoderCommon,
70    controller: &TransformStreamDefaultController,
71) -> Fallible<()> {
72    // Step 1. Let output be the I/O queue of scalar values « end-of-queue ».
73    // Step 2. While true:
74    // Step 2.1 Let item be the result of reading from decoder’s I/O queue.
75    // Step 2.2 Let result be the result of processing an item with item,
76    //      decoder’s decoder, decoder’s I/O queue, output, and decoder’s error mode.
77    // Step 2.3 If result is finished:
78    // Step 2.3.1 Let outputChunk be the result of running serialize I/O queue
79    //      with decoder and output.
80    // Step 2.3.3 Return.
81    // Step 2.3.4 Otherwise, if result is error, throw a TypeError.
82    let output_chunk = decoder.decode(cx.no_gc(), None, true)?;
83
84    // Step 2.3.2 If outputChunk is not the empty string, then enqueue
85    //      outputChunk in decoder’s transform.
86    if output_chunk.is_empty() {
87        return Ok(());
88    }
89    rooted!(&in(cx) let mut rval = UndefinedValue());
90    output_chunk.safe_to_jsval(cx, rval.handle_mut());
91    controller.enqueue(cx, global, rval.handle())
92}
93
94/// <https://encoding.spec.whatwg.org/#textdecoderstream>
95#[dom_struct]
96pub(crate) struct TextDecoderStream {
97    reflector_: Reflector,
98
99    /// <https://encoding.spec.whatwg.org/#textdecodercommon>
100    #[conditional_malloc_size_of]
101    decoder: Rc<TextDecoderCommon>,
102
103    /// <https://streams.spec.whatwg.org/#generictransformstream>
104    transform: Dom<TransformStream>,
105}
106
107#[expect(non_snake_case)]
108impl TextDecoderStream {
109    fn new_inherited(
110        decoder: Rc<TextDecoderCommon>,
111        transform: &TransformStream,
112    ) -> TextDecoderStream {
113        TextDecoderStream {
114            reflector_: Reflector::new(),
115            decoder,
116            transform: Dom::from_ref(transform),
117        }
118    }
119
120    pub(crate) fn new_with_proto(
121        cx: &mut js::context::JSContext,
122        global: &GlobalScope,
123        proto: Option<SafeHandleObject>,
124        encoding: &'static Encoding,
125        fatal: bool,
126        ignoreBOM: bool,
127    ) -> Fallible<DomRoot<Self>> {
128        let decoder = Rc::new(TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM));
129
130        let transform_stream = TransformStream::new_with_proto(cx, global, None);
131        transform_stream.set_up(cx, global, TransformerType::Decoder(decoder.clone()))?;
132
133        Ok(reflect_dom_object_with_proto(
134            cx,
135            Box::new(TextDecoderStream::new_inherited(decoder, &transform_stream)),
136            global,
137            proto,
138        ))
139    }
140}
141
142impl TextDecoderStreamMethods<crate::DomTypeHolder> for TextDecoderStream {
143    /// <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
144    fn Constructor(
145        cx: &mut js::context::JSContext,
146        global: &GlobalScope,
147        proto: Option<SafeHandleObject>,
148        label: DOMString,
149        options: &TextDecoderBinding::TextDecoderOptions,
150    ) -> Fallible<DomRoot<TextDecoderStream>> {
151        let encoding = match Encoding::for_label_no_replacement(&label.as_bytes()) {
152            Some(enc) => enc,
153            None => {
154                return Err(Error::Range(
155                    c"The given encoding is not supported".to_owned(),
156                ));
157            },
158        };
159
160        Self::new_with_proto(
161            cx,
162            global,
163            proto,
164            encoding,
165            options.fatal,
166            options.ignoreBOM,
167        )
168    }
169
170    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-encoding>
171    fn Encoding(&self) -> DOMString {
172        DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
173    }
174
175    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-fatal>
176    fn Fatal(&self) -> bool {
177        self.decoder.fatal()
178    }
179
180    /// <https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom>
181    fn IgnoreBOM(&self) -> bool {
182        self.decoder.ignore_bom()
183    }
184
185    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
186    fn Readable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::ReadableStream> {
187        self.transform.get_readable()
188    }
189
190    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
191    fn Writable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::WritableStream> {
192        self.transform.get_writable()
193    }
194}