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}