Skip to main content

script/dom/encoding/
textencoderstream.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::cell::Cell;
6use std::num::{NonZero, NonZeroU16};
7use std::ptr;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::conversions::ToJSValConvertible;
12use js::jsapi::{JSObject, JSString};
13use js::jsval::UndefinedValue;
14use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
15use js::typedarray::Uint8;
16use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
17use script_bindings::str::js_string_to_code_units;
18
19use crate::dom::bindings::buffer_source::create_buffer_source;
20use crate::dom::bindings::codegen::Bindings::TextEncoderStreamBinding::TextEncoderStreamMethods;
21use crate::dom::bindings::error::{Error, Fallible};
22use crate::dom::bindings::root::{Dom, DomRoot};
23use crate::dom::bindings::str::{ConversionResult, DOMString};
24use crate::dom::stream::readablestream::ReadableStream;
25use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
26use crate::dom::stream::writablestream::WritableStream;
27use crate::dom::types::{GlobalScope, TransformStream, TransformStreamDefaultController};
28
29/// <https://encoding.spec.whatwg.org/#textencoderstream-encoder>
30#[derive(Default, JSTraceable, MallocSizeOf)]
31pub(crate) struct Encoder {
32    /// <https://encoding.spec.whatwg.org/#textencoderstream-pending-high-surrogate>
33    leading_surrogate: Cell<Option<NonZeroU16>>,
34}
35
36impl Encoder {
37    fn encode(&self, maybe_ill_formed: ConversionResult) -> String {
38        match maybe_ill_formed {
39            ConversionResult::String(s) => {
40                // Rust String is already UTF-8 encoded and cannot contain
41                // surrogate
42                if !s.is_empty() && self.leading_surrogate.take().is_some() {
43                    let mut output = String::with_capacity(1 + s.len());
44                    output.push('\u{FFFD}');
45                    output.push_str(&s);
46                    return output;
47                }
48
49                s
50            },
51            ConversionResult::CodeUnits(code_units) => self.encode_from_code_units(&code_units.0),
52        }
53    }
54
55    /// Encode an input slice of code unit into unicode scalar values
56    fn encode_from_code_units(&self, input: &[u16]) -> String {
57        // <https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk>
58        //
59        // Step 3. Let output be the I/O queue of bytes « end-of-queue ».
60        let mut output = String::with_capacity(input.len());
61        // Step 4. While true:
62        // Step 4.1 Let item be the result of reading from input.
63        for result in char::decode_utf16(input.iter().cloned()) {
64            // Step 4.3 Let result be the result of executing the convert code unit
65            //      to scalar value algorithm with encoder, item and input.
66
67            // <https://encoding.spec.whatwg.org/#convert-code-unit-to-scalar-value>
68            match result {
69                Ok(c) => {
70                    // Step 1. If encoder’s leading surrogate is non-null:
71                    // Step 1.1 Let leadingSurrogate be encoder’s leading surrogate.
72                    // Step 1.2 Set encoder’s leading surrogate to null.
73                    if self.leading_surrogate.take().is_some() {
74                        // Step 1.5 Return U+FFFD (�).
75                        output.push('\u{FFFD}');
76                    }
77
78                    // Step 1.4 Restore item to input.
79                    // Note: pushing item to output is equivalent to restoring item to input
80                    //      and rerun the convert-code-unit-to-scalar-value algo
81                    output.push(c);
82                },
83                Err(error) => {
84                    let unpaired_surrogate = error.unpaired_surrogate();
85                    match code_point_type(unpaired_surrogate) {
86                        CodePointType::LeadingSurrogate => {
87                            // Step 1.1 If encoder’s leading surrogate is non-null:
88                            // Step 1.2 Set encoder’s leading surrogate to null.
89                            if self.leading_surrogate.take().is_some() {
90                                output.push('\u{FFFD}');
91                            }
92
93                            // Step 1.4 Restore item to input.
94                            // Note: Replacing encoder's leading_surrogate is equivalent
95                            //      to restore item back to input and rerun the convert-
96                            //      code-unit-to-scalar-value algo.
97                            // Step 2. If item is a leading surrogate, then set encoder’s
98                            //      leading surrogate to item and return continue.
99                            self.leading_surrogate
100                                .replace(NonZero::new(unpaired_surrogate));
101                        },
102                        CodePointType::TrailingSurrogate => match self.leading_surrogate.take() {
103                            // Step 1.1 If encoder’s leading surrogate is non-null:
104                            // Step 1.2 Set encoder’s leading surrogate to null.
105                            Some(leading_surrogate) => {
106                                // Step 1.3 If item is a trailing surrogate, then return a scalar
107                                //      value from surrogates given leadingSurrogate and item.
108                                let c = char::decode_utf16([
109                                    leading_surrogate.get(),
110                                    unpaired_surrogate,
111                                ])
112                                .next()
113                                .expect("A pair of surrogate is supplied")
114                                .expect("Decoding a pair of surrogate cannot fail");
115                                output.push(c);
116                            },
117                            // Step 3. If item is a trailing surrogate, then return U+FFFD (�).
118                            None => output.push('\u{FFFD}'),
119                        },
120                        CodePointType::ScalarValue => unreachable!("Scalar Value won't fail"),
121                    }
122                },
123            }
124        }
125
126        output
127    }
128}
129
130enum CodePointType {
131    ScalarValue,
132    LeadingSurrogate,
133    TrailingSurrogate,
134}
135
136fn code_point_type(value: u16) -> CodePointType {
137    match value {
138        0xD800..=0xDBFF => CodePointType::LeadingSurrogate,
139        0xDC00..=0xDFFF => CodePointType::TrailingSurrogate,
140        _ => CodePointType::ScalarValue,
141    }
142}
143
144/// <https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk>
145pub(crate) fn encode_and_enqueue_a_chunk(
146    cx: &mut JSContext,
147    global: &GlobalScope,
148    chunk: SafeHandleValue,
149    encoder: &Encoder,
150    controller: &TransformStreamDefaultController,
151) -> Fallible<()> {
152    // Step 1. Let input be the result of converting chunk to a DOMString.
153    // Note: using not a DOMString but ConversionResult,
154    // because a DOMString assumes utf-8.
155    rooted!(&in(cx) let mut target = ptr::null_mut::<JSString>());
156    let input = js_string_to_code_units(cx, chunk, target.handle_mut())?;
157
158    // Step 2. Convert input to an I/O queue of code units.
159    // Note: passing input as a slice.
160
161    // Step 3. Let output be the I/O queue of bytes « end-of-queue ».
162    // Step 4. While true:
163    // Step 4.1 Let item be the result of reading from input.
164    // Step 4.3 Let result be the result of executing the convert code unit
165    //      to scalar value algorithm with encoder, item and input.
166    // Step 4.4 If result is not continue, then process an item with result,
167    //      encoder’s encoder, input, output, and "fatal".
168    let output = encoder.encode(input);
169
170    // Step 4.2 If item is end-of-queue:
171    // Step 4.2.1 Convert output into a byte sequence.
172    let output = output.as_bytes();
173    // Step 4.2.2 If output is not empty:
174    if output.is_empty() {
175        // Step 4.2.3
176        return Ok(());
177    }
178
179    // Step 4.2.2.1 Let chunk be the result of creating a Uint8Array object
180    //      given output and encoder’s relevant realm.
181    rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
182    let chunk = create_buffer_source::<Uint8>(cx, output, js_object.handle_mut())
183        .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
184    rooted!(&in(cx) let mut rval = UndefinedValue());
185    chunk.to_jsval(cx, rval.handle_mut());
186    // Step 4.2.2.2 Enqueue chunk into encoder’s transform.
187    controller.enqueue(cx, global, rval.handle())?;
188    Ok(())
189}
190
191/// <https://encoding.spec.whatwg.org/#encode-and-flush>
192pub(crate) fn encode_and_flush(
193    cx: &mut JSContext,
194    global: &GlobalScope,
195    encoder: &Encoder,
196    controller: &TransformStreamDefaultController,
197) -> Fallible<()> {
198    // Step 1. If encoder’s leading surrogate is non-null:
199    if encoder.leading_surrogate.get().is_some() {
200        // Step 1.1 Let chunk be the result of creating a Uint8Array object
201        //      given « 0xEF, 0xBF, 0xBD » and encoder’s relevant realm.
202        rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
203        let chunk =
204            create_buffer_source::<Uint8>(cx, &[0xEF_u8, 0xBF, 0xBD], js_object.handle_mut())
205                .map_err(|_| {
206                    Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned())
207                })?;
208        rooted!(&in(cx) let mut rval = UndefinedValue());
209        chunk.to_jsval(cx, rval.handle_mut());
210        // Step 1.2 Enqueue chunk into encoder’s transform.
211        return controller.enqueue(cx, global, rval.handle());
212    }
213
214    Ok(())
215}
216
217/// <https://encoding.spec.whatwg.org/#textencoderstream>
218#[dom_struct]
219pub(crate) struct TextEncoderStream {
220    reflector_: Reflector,
221
222    /// <https://streams.spec.whatwg.org/#generictransformstream>
223    transform: Dom<TransformStream>,
224}
225
226impl TextEncoderStream {
227    fn new_inherited(transform: &TransformStream) -> TextEncoderStream {
228        Self {
229            reflector_: Reflector::new(),
230            transform: Dom::from_ref(transform),
231        }
232    }
233
234    /// <https://encoding.spec.whatwg.org/#dom-textencoderstream>
235    fn new_with_proto(
236        cx: &mut JSContext,
237        global: &GlobalScope,
238        proto: Option<SafeHandleObject>,
239    ) -> Fallible<DomRoot<TextEncoderStream>> {
240        // Step 1. Set this’s encoder to an instance of the UTF-8 encoder.
241        let encoder = Encoder::default();
242
243        // Step 2. Let transformAlgorithm be an algorithm which takes a chunk argument
244        //      and runs the encode and enqueue a chunk algorithm with this and chunk.
245        // Step 3. Let flushAlgorithm be an algorithm which runs the encode and flush
246        //      algorithm with this.
247
248        // Step 4. Let transformStream be a new TransformStream.
249        let transform = TransformStream::new_with_proto(cx, global, None);
250        // Step 5. Set up transformStream with transformAlgorithm set to transformAlgorithm
251        //      and flushAlgorithm set to flushAlgorithm.
252        transform.set_up(cx, global, TransformerType::Encoder(encoder))?;
253
254        // Step 6. Set this’s transform to transformStream.
255        Ok(reflect_dom_object_with_proto(
256            cx,
257            Box::new(TextEncoderStream::new_inherited(&transform)),
258            global,
259            proto,
260        ))
261    }
262}
263
264impl TextEncoderStreamMethods<crate::DomTypeHolder> for TextEncoderStream {
265    /// <https://encoding.spec.whatwg.org/#dom-textencoderstream>
266    fn Constructor(
267        cx: &mut JSContext,
268        global: &GlobalScope,
269        proto: Option<SafeHandleObject>,
270    ) -> Fallible<DomRoot<TextEncoderStream>> {
271        TextEncoderStream::new_with_proto(cx, global, proto)
272    }
273
274    /// <https://encoding.spec.whatwg.org/#dom-textencoder-encoding>
275    fn Encoding(&self) -> DOMString {
276        // Returns "utf-8".
277        DOMString::from_static("utf-8")
278    }
279
280    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
281    fn Readable(&self) -> DomRoot<ReadableStream> {
282        self.transform.get_readable()
283    }
284
285    /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
286    fn Writable(&self) -> DomRoot<WritableStream> {
287        self.transform.get_writable()
288    }
289}