/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::rc::Rc; use dom_struct::dom_struct; use encoding_rs::Encoding; use js::conversions::{FromJSValConvertible, ToJSValConvertible}; use js::jsval::UndefinedValue; use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}; use crate::DomTypes; use crate::dom::bindings::codegen::Bindings::TextDecoderBinding; use crate::dom::bindings::codegen::Bindings::TextDecoderStreamBinding::TextDecoderStreamMethods; use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::globalscope::GlobalScope; use crate::dom::textdecodercommon::TextDecoderCommon; use crate::dom::transformstreamdefaultcontroller::TransformerType; use crate::dom::types::{TransformStream, TransformStreamDefaultController}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; /// #[allow(unsafe_code)] pub(crate) fn decode_and_enqueue_a_chunk( cx: SafeJSContext, global: &GlobalScope, chunk: SafeHandleValue, decoder: &TextDecoderCommon, controller: &TransformStreamDefaultController, can_gc: CanGc, ) -> Fallible<()> { // Step 1. Let bufferSource be the result of converting chunk to an AllowSharedBufferSource. let conversion_result = unsafe { ArrayBufferViewOrArrayBuffer::from_jsval(*cx, chunk, ()).map_err(|_| { Error::Type("Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_string()) })? }; let buffer_source = conversion_result.get_success_value().ok_or_else(|| { Error::Type("Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_string()) })?; // Step 2. Push a copy of bufferSource to decoder’s I/O queue. // Step 3. Let output be the I/O queue of scalar values « end-of-queue ». // Step 4. While true: // Step 4.1 Let item be the result of reading from decoder’s I/O queue. // Step 4.2 If item is end-of-queue: // Step 4.2.1 Let outputChunk be the result of running serialize I/O queue with decoder and output. // Step 4.2.3 Return. // Step 4.3 Let result be the result of processing an item with item, decoder’s decoder, // decoder’s I/O queue, output, and decoder’s error mode. // Step 4.4 If result is error, then throw a TypeError. let output_chunk = decoder.decode(Some(buffer_source), false)?; // Step 4.2.2 If outputChunk is not the empty string, then enqueue // outputChunk in decoder’s transform. if output_chunk.is_empty() { return Ok(()); } rooted!(in(*cx) let mut rval = UndefinedValue()); unsafe { output_chunk.to_jsval(*cx, rval.handle_mut()) }; controller.enqueue(cx, global, rval.handle(), can_gc) } /// #[allow(unsafe_code)] pub(crate) fn flush_and_enqueue( cx: SafeJSContext, global: &GlobalScope, decoder: &TextDecoderCommon, controller: &TransformStreamDefaultController, can_gc: CanGc, ) -> Fallible<()> { // Step 1. Let output be the I/O queue of scalar values « end-of-queue ». // Step 2. While true: // Step 2.1 Let item be the result of reading from decoder’s I/O queue. // Step 2.2 Let result be the result of processing an item with item, // decoder’s decoder, decoder’s I/O queue, output, and decoder’s error mode. // Step 2.3 If result is finished: // Step 2.3.1 Let outputChunk be the result of running serialize I/O queue // with decoder and output. // Step 2.3.3 Return. // Step 2.3.4 Otherwise, if result is error, throw a TypeError. let output_chunk = decoder.decode(None, true)?; // Step 2.3.2 If outputChunk is not the empty string, then enqueue // outputChunk in decoder’s transform. if output_chunk.is_empty() { return Ok(()); } rooted!(in(*cx) let mut rval = UndefinedValue()); unsafe { output_chunk.to_jsval(*cx, rval.handle_mut()) }; controller.enqueue(cx, global, rval.handle(), can_gc) } /// #[dom_struct] pub(crate) struct TextDecoderStream { reflector_: Reflector, /// #[ignore_malloc_size_of = "Rc is hard"] decoder: Rc, /// transform: Dom, } #[allow(non_snake_case)] impl TextDecoderStream { fn new_inherited( decoder: Rc, transform: &TransformStream, ) -> TextDecoderStream { TextDecoderStream { reflector_: Reflector::new(), decoder, transform: Dom::from_ref(transform), } } fn new_with_proto( cx: SafeJSContext, global: &GlobalScope, proto: Option, encoding: &'static Encoding, fatal: bool, ignoreBOM: bool, can_gc: CanGc, ) -> Fallible> { let decoder = Rc::new(TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM)); let transformer_type = TransformerType::Decoder(decoder.clone()); let transform_stream = TransformStream::new_with_proto(global, None, can_gc); transform_stream.set_up(cx, global, transformer_type, can_gc)?; Ok(reflect_dom_object_with_proto( Box::new(TextDecoderStream::new_inherited(decoder, &transform_stream)), global, proto, can_gc, )) } } #[allow(non_snake_case)] impl TextDecoderStreamMethods for TextDecoderStream { /// fn Constructor( global: &GlobalScope, proto: Option, can_gc: CanGc, label: DOMString, options: &TextDecoderBinding::TextDecoderOptions, ) -> Fallible> { let encoding = match Encoding::for_label_no_replacement(label.as_bytes()) { Some(enc) => enc, None => { return Err(Error::Range( "The given encoding is not supported".to_owned(), )); }, }; Self::new_with_proto( GlobalScope::get_cx(), global, proto, encoding, options.fatal, options.ignoreBOM, can_gc, ) } /// fn Encoding(&self) -> DOMString { DOMString::from(self.decoder.encoding().name().to_ascii_lowercase()) } /// fn Fatal(&self) -> bool { self.decoder.fatal() } /// fn IgnoreBOM(&self) -> bool { self.decoder.ignore_bom() } /// fn Readable(&self) -> DomRoot<::ReadableStream> { self.transform.get_readable() } /// fn Writable(&self) -> DomRoot<::WritableStream> { self.transform.get_writable() } }