use std::cell::Cell;
use std::num::{NonZero, NonZeroU16};
use std::ptr;
use dom_struct::dom_struct;
use js::context::JSContext;
use js::conversions::ToJSValConvertible;
use js::jsapi::{JSObject, JSString};
use js::jsval::UndefinedValue;
use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
use js::typedarray::Uint8;
use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use script_bindings::str::js_string_to_code_units;
use crate::dom::bindings::buffer_source::create_buffer_source;
use crate::dom::bindings::codegen::Bindings::TextEncoderStreamBinding::TextEncoderStreamMethods;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{ConversionResult, DOMString};
use crate::dom::stream::readablestream::ReadableStream;
use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
use crate::dom::stream::writablestream::WritableStream;
use crate::dom::types::{GlobalScope, TransformStream, TransformStreamDefaultController};
#[derive(Default, JSTraceable, MallocSizeOf)]
pub(crate) struct Encoder {
leading_surrogate: Cell<Option<NonZeroU16>>,
}
impl Encoder {
fn encode(&self, maybe_ill_formed: ConversionResult) -> String {
match maybe_ill_formed {
ConversionResult::String(s) => {
if !s.is_empty() && self.leading_surrogate.take().is_some() {
let mut output = String::with_capacity(1 + s.len());
output.push('\u{FFFD}');
output.push_str(&s);
return output;
}
s
},
ConversionResult::CodeUnits(code_units) => self.encode_from_code_units(&code_units.0),
}
}
fn encode_from_code_units(&self, input: &[u16]) -> String {
let mut output = String::with_capacity(input.len());
for result in char::decode_utf16(input.iter().cloned()) {
match result {
Ok(c) => {
if self.leading_surrogate.take().is_some() {
output.push('\u{FFFD}');
}
output.push(c);
},
Err(error) => {
let unpaired_surrogate = error.unpaired_surrogate();
match code_point_type(unpaired_surrogate) {
CodePointType::LeadingSurrogate => {
if self.leading_surrogate.take().is_some() {
output.push('\u{FFFD}');
}
self.leading_surrogate
.replace(NonZero::new(unpaired_surrogate));
},
CodePointType::TrailingSurrogate => match self.leading_surrogate.take() {
Some(leading_surrogate) => {
let c = char::decode_utf16([
leading_surrogate.get(),
unpaired_surrogate,
])
.next()
.expect("A pair of surrogate is supplied")
.expect("Decoding a pair of surrogate cannot fail");
output.push(c);
},
None => output.push('\u{FFFD}'),
},
CodePointType::ScalarValue => unreachable!("Scalar Value won't fail"),
}
},
}
}
output
}
}
enum CodePointType {
ScalarValue,
LeadingSurrogate,
TrailingSurrogate,
}
fn code_point_type(value: u16) -> CodePointType {
match value {
0xD800..=0xDBFF => CodePointType::LeadingSurrogate,
0xDC00..=0xDFFF => CodePointType::TrailingSurrogate,
_ => CodePointType::ScalarValue,
}
}
pub(crate) fn encode_and_enqueue_a_chunk(
cx: &mut JSContext,
global: &GlobalScope,
chunk: SafeHandleValue,
encoder: &Encoder,
controller: &TransformStreamDefaultController,
) -> Fallible<()> {
rooted!(&in(cx) let mut target = ptr::null_mut::<JSString>());
let input = js_string_to_code_units(cx, chunk, target.handle_mut())?;
let output = encoder.encode(input);
let output = output.as_bytes();
if output.is_empty() {
return Ok(());
}
rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
let chunk = create_buffer_source::<Uint8>(cx, output, js_object.handle_mut())
.map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
rooted!(&in(cx) let mut rval = UndefinedValue());
chunk.safe_to_jsval(cx, rval.handle_mut());
controller.enqueue(cx, global, rval.handle())?;
Ok(())
}
pub(crate) fn encode_and_flush(
cx: &mut JSContext,
global: &GlobalScope,
encoder: &Encoder,
controller: &TransformStreamDefaultController,
) -> Fallible<()> {
if encoder.leading_surrogate.get().is_some() {
rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
let chunk =
create_buffer_source::<Uint8>(cx, &[0xEF_u8, 0xBF, 0xBD], js_object.handle_mut())
.map_err(|_| {
Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned())
})?;
rooted!(&in(cx) let mut rval = UndefinedValue());
chunk.safe_to_jsval(cx, rval.handle_mut());
return controller.enqueue(cx, global, rval.handle());
}
Ok(())
}
#[dom_struct]
pub(crate) struct TextEncoderStream {
reflector_: Reflector,
transform: Dom<TransformStream>,
}
impl TextEncoderStream {
fn new_inherited(transform: &TransformStream) -> TextEncoderStream {
Self {
reflector_: Reflector::new(),
transform: Dom::from_ref(transform),
}
}
fn new_with_proto(
cx: &mut JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
) -> Fallible<DomRoot<TextEncoderStream>> {
let encoder = Encoder::default();
let transform = TransformStream::new_with_proto(cx, global, None);
transform.set_up(cx, global, TransformerType::Encoder(encoder))?;
Ok(reflect_dom_object_with_proto(
cx,
Box::new(TextEncoderStream::new_inherited(&transform)),
global,
proto,
))
}
}
impl TextEncoderStreamMethods<crate::DomTypeHolder> for TextEncoderStream {
fn Constructor(
cx: &mut JSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
) -> Fallible<DomRoot<TextEncoderStream>> {
TextEncoderStream::new_with_proto(cx, global, proto)
}
fn Encoding(&self) -> DOMString {
DOMString::from_static("utf-8")
}
fn Readable(&self) -> DomRoot<ReadableStream> {
self.transform.get_readable()
}
fn Writable(&self) -> DomRoot<WritableStream> {
self.transform.get_writable()
}
}