Script: Implement TextDecoderStream (#38112)

This PR implements the `TextDecoderStream`. Other than introducing the
necessary mod and webidl files corresponding to `TextDecoderStream`,
this PR also involves some changes in `TextDecoder` and
`TrasnformStream`:

- The common part that can be shared between `TextDecoder` and
`TextDecoderStream` are extracted into a separate type
`script::dom::textdecodercommon::TextDecoderCommon`. This type could
probably use a different name because there is an interface called
`TextDecoderCommon` in the spec
(https://encoding.spec.whatwg.org/#textdecodercommon) which just gets
included in `TextDecoder` and `TextDecoderStream`.
- The three algorithms in `TransformStream` (`cancel`, `flush`, and
`transform`) all have become `enum` that has a `Js` variant for a JS
function object and a `Native` variant for a rust trait object. Whether
the cancel algorithm needs this enum type is debatable as I did not find
any interface in the spec that explicitly sets the cancel algorithm.

Testing: Existing WPT tests `tests/wpt/tests/encoding/stream` should be
sufficient
Fixes: #37723

---------

Signed-off-by: minghuaw <michael.wu1107@gmail.com>
Signed-off-by: minghuaw <wuminghua7@huawei.com>
Signed-off-by: Minghua Wu <michael.wu1107@gmail.com>
This commit is contained in:
minghuaw 2025-07-29 12:18:15 +08:00 committed by GitHub
parent 25822920cf
commit 554b2da1ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 797 additions and 752 deletions

View file

@ -590,6 +590,8 @@ pub(crate) mod testworkletglobalscope;
pub(crate) mod text; pub(crate) mod text;
pub(crate) mod textcontrol; pub(crate) mod textcontrol;
pub(crate) mod textdecoder; pub(crate) mod textdecoder;
pub(crate) mod textdecodercommon;
pub(crate) mod textdecoderstream;
pub(crate) mod textencoder; pub(crate) mod textencoder;
pub(crate) mod textmetrics; pub(crate) mod textmetrics;
pub(crate) mod texttrack; pub(crate) mod texttrack;

View file

@ -3,10 +3,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::cell::{Cell, RefCell}; use std::cell::Cell;
use dom_struct::dom_struct; use dom_struct::dom_struct;
use encoding_rs::{Decoder, DecoderResult, Encoding}; use encoding_rs::Encoding;
use js::rust::HandleObject; use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::TextDecoderBinding; use crate::dom::bindings::codegen::Bindings::TextDecoderBinding;
@ -19,37 +19,29 @@ use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::textdecodercommon::TextDecoderCommon;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
/// <https://encoding.spec.whatwg.org/#textdecoder>
#[dom_struct] #[dom_struct]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub(crate) struct TextDecoder { pub(crate) struct TextDecoder {
reflector_: Reflector, reflector_: Reflector,
#[no_trace]
encoding: &'static Encoding, /// <https://encoding.spec.whatwg.org/#textdecodercommon>
fatal: bool, decoder: TextDecoderCommon,
ignoreBOM: bool,
#[ignore_malloc_size_of = "defined in encoding_rs"] /// <https://encoding.spec.whatwg.org/#textdecoder-do-not-flush-flag>
#[no_trace]
decoder: RefCell<Decoder>,
in_stream: RefCell<Vec<u8>>,
do_not_flush: Cell<bool>, do_not_flush: Cell<bool>,
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
impl TextDecoder { impl TextDecoder {
fn new_inherited(encoding: &'static Encoding, fatal: bool, ignoreBOM: bool) -> TextDecoder { fn new_inherited(encoding: &'static Encoding, fatal: bool, ignoreBOM: bool) -> TextDecoder {
let decoder = TextDecoderCommon::new_inherited(encoding, fatal, ignoreBOM);
TextDecoder { TextDecoder {
reflector_: Reflector::new(), reflector_: Reflector::new(),
encoding, decoder,
fatal,
ignoreBOM,
decoder: RefCell::new(if ignoreBOM {
encoding.new_decoder()
} else {
encoding.new_decoder_without_bom_handling()
}),
in_stream: RefCell::new(Vec::new()),
do_not_flush: Cell::new(false), do_not_flush: Cell::new(false),
} }
} }
@ -77,6 +69,7 @@ impl TextDecoder {
} }
} }
#[allow(non_snake_case)]
impl TextDecoderMethods<crate::DomTypeHolder> for TextDecoder { impl TextDecoderMethods<crate::DomTypeHolder> for TextDecoder {
/// <https://encoding.spec.whatwg.org/#dom-textdecoder> /// <https://encoding.spec.whatwg.org/#dom-textdecoder>
fn Constructor( fn Constructor(
@ -100,85 +93,59 @@ impl TextDecoderMethods<crate::DomTypeHolder> for TextDecoder {
)) ))
} }
// https://encoding.spec.whatwg.org/#dom-textdecoder-encoding /// <https://encoding.spec.whatwg.org/#dom-textdecoder-encoding>
fn Encoding(&self) -> DOMString { fn Encoding(&self) -> DOMString {
DOMString::from(self.encoding.name().to_ascii_lowercase()) DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
} }
// https://encoding.spec.whatwg.org/#dom-textdecoder-fatal /// <https://encoding.spec.whatwg.org/#dom-textdecoder-fatal>
fn Fatal(&self) -> bool { fn Fatal(&self) -> bool {
self.fatal self.decoder.fatal()
} }
// https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom /// <https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom>
fn IgnoreBOM(&self) -> bool { fn IgnoreBOM(&self) -> bool {
self.ignoreBOM self.decoder.ignore_bom()
} }
// https://encoding.spec.whatwg.org/#dom-textdecoder-decode /// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
fn Decode( fn Decode(
&self, &self,
input: Option<ArrayBufferViewOrArrayBuffer>, input: Option<ArrayBufferViewOrArrayBuffer>,
options: &TextDecodeOptions, options: &TextDecodeOptions,
) -> Fallible<USVString> { ) -> Fallible<USVString> {
// Step 1. // Step 1. If thiss do not flush is false, then set thiss decoder to a new
// instance of thiss encodings decoder, thiss I/O queue to the I/O queue
// of bytes « end-of-queue », and thiss BOM seen to false.
if !self.do_not_flush.get() { if !self.do_not_flush.get() {
if self.ignoreBOM { if self.decoder.ignore_bom() {
self.decoder self.decoder
.replace(self.encoding.new_decoder_without_bom_handling()); .decoder()
.replace(self.decoder.encoding().new_decoder_without_bom_handling());
} else { } else {
self.decoder.replace(self.encoding.new_decoder()); self.decoder
.decoder()
.replace(self.decoder.encoding().new_decoder_with_bom_removal());
} }
self.in_stream.replace(Vec::new()); self.decoder.io_queue().replace(Vec::new());
} }
// Step 2. // Step 2. Set thiss do not flush to options["stream"].
self.do_not_flush.set(options.stream); self.do_not_flush.set(options.stream);
// Step 3. // Step 3. If input is given, then push a copy of input to thiss I/O queue.
match input { // Step 4. Let output be the I/O queue of scalar values « end-of-queue ».
Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(ref a)) => { // Step 5. While true:
self.in_stream.borrow_mut().extend_from_slice(&a.to_vec()); // Step 5.1 Let item be the result of reading from thiss I/O queue.
}, // Step 5.2 If item is end-of-queue and thiss do not flush is true,
Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(ref a)) => { // then return the result of running serialize I/O queue with this and output.
self.in_stream.borrow_mut().extend_from_slice(&a.to_vec()); // Step 5.3 Otherwise:
}, // Step 5.3.1 Let result be the result of processing an item with item, thiss decoder,
None => {}, // thiss I/O queue, output, and thiss error mode.
}; // Step 5.3.2 If result is finished, then return the result of running serialize I/O
// queue with this and output.
let mut decoder = self.decoder.borrow_mut(); self.decoder
let (remaining, s) = { .decode(input.as_ref(), !options.stream)
let mut in_stream = self.in_stream.borrow_mut(); .map(USVString)
let (remaining, s) = if self.fatal {
// Step 4.
let mut out_stream = String::with_capacity(
decoder
.max_utf8_buffer_length_without_replacement(in_stream.len())
.unwrap(),
);
// Step 5: Implemented by encoding_rs::Decoder.
match decoder.decode_to_string_without_replacement(
&in_stream,
&mut out_stream,
!options.stream,
) {
(DecoderResult::InputEmpty, read) => (in_stream.split_off(read), out_stream),
// Step 5.3.3.
_ => return Err(Error::Type("Decoding failed".to_owned())),
}
} else {
// Step 4.
let mut out_stream =
String::with_capacity(decoder.max_utf8_buffer_length(in_stream.len()).unwrap());
// Step 5: Implemented by encoding_rs::Decoder.
let (_result, read, _replaced) =
decoder.decode_to_string(&in_stream, &mut out_stream, !options.stream);
(in_stream.split_off(read), out_stream)
};
(remaining, s)
};
self.in_stream.replace(remaining);
Ok(USVString(s))
} }
} }

View file

@ -0,0 +1,227 @@
/* 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::cell::RefCell;
use encoding_rs::{Decoder, DecoderResult, Encoding};
use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
use crate::dom::bindings::error::{Error, Fallible};
/// The shared part of `TextDecoder` and `TextDecoderStream`
///
/// Note that other than the three attributes defined in the `TextDecoderCommon`
/// interface in the WebIDL, this also performs decoding.
///
/// <https://encoding.spec.whatwg.org/#textdecodercommon>
#[allow(non_snake_case)]
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct TextDecoderCommon {
/// <https://encoding.spec.whatwg.org/#dom-textdecoder-encoding>
#[no_trace]
encoding: &'static Encoding,
/// <https://encoding.spec.whatwg.org/#dom-textdecoder-fatal>
fatal: bool,
/// <https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom>
ignoreBOM: bool,
/// The native decoder that is used to perform decoding
///
/// <https://encoding.spec.whatwg.org/#textdecodercommon-decoder>
#[ignore_malloc_size_of = "defined in encoding_rs"]
#[no_trace]
decoder: RefCell<Decoder>,
/// <https://encoding.spec.whatwg.org/#textdecodercommon-i-o-queue>
io_queue: RefCell<Vec<u8>>,
}
#[allow(non_snake_case)]
impl TextDecoderCommon {
pub(crate) fn new_inherited(
encoding: &'static Encoding,
fatal: bool,
ignoreBOM: bool,
) -> TextDecoderCommon {
let decoder = if ignoreBOM {
encoding.new_decoder_without_bom_handling()
} else {
encoding.new_decoder_with_bom_removal()
};
TextDecoderCommon {
encoding,
fatal,
ignoreBOM,
decoder: RefCell::new(decoder),
io_queue: RefCell::new(Vec::new()),
}
}
/// <https://encoding.spec.whatwg.org/#textdecoder-encoding>
pub(crate) fn encoding(&self) -> &'static Encoding {
self.encoding
}
/// <https://encoding.spec.whatwg.org/#textdecodercommon-decoder>
pub(crate) fn decoder(&self) -> &RefCell<Decoder> {
&self.decoder
}
/// <https://encoding.spec.whatwg.org/#textdecodercommon-i-o-queue>
pub(crate) fn io_queue(&self) -> &RefCell<Vec<u8>> {
&self.io_queue
}
/// <https://encoding.spec.whatwg.org/#textdecoder-error-mode>
pub(crate) fn fatal(&self) -> bool {
self.fatal
}
/// <https://encoding.spec.whatwg.org/#textdecoder-ignore-bom-flag>
pub(crate) fn ignore_bom(&self) -> bool {
self.ignoreBOM
}
/// Shared by `TextDecoder` and `TextDecoderStream`
///
/// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
/// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
#[allow(unsafe_code)]
pub(crate) fn decode(
&self,
input: Option<&ArrayBufferViewOrArrayBuffer>,
last: bool,
) -> Fallible<String> {
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 3. If input is given, then push a copy of input to thiss I/O queue.
//
// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
// Step 2. Push a copy of bufferSource to decoders I/O queue.
//
// NOTE: try to avoid this copy unless there are bytes left
let mut io_queue = self.io_queue.borrow_mut();
let input = match &input {
Some(ArrayBufferViewOrArrayBuffer::ArrayBufferView(a)) => unsafe {
if io_queue.is_empty() {
a.as_slice()
} else {
io_queue.extend_from_slice(a.as_slice());
&io_queue[..]
}
},
Some(ArrayBufferViewOrArrayBuffer::ArrayBuffer(a)) => unsafe {
if io_queue.is_empty() {
a.as_slice()
} else {
io_queue.extend_from_slice(a.as_slice());
&io_queue[..]
}
},
None => &io_queue[..],
};
let mut decoder = self.decoder.borrow_mut();
let (output, read) = if self.fatal {
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 4. Let output be the I/O queue of scalar values « end-of-queue ».
//
// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
// Step 3. Let output be the I/O queue of scalar values « end-of-queue ».
let mut output = String::with_capacity(
decoder
.max_utf8_buffer_length_without_replacement(input.len())
.ok_or_else(|| {
Error::Type("Expected UTF8 buffer length would overflow".to_owned())
})?,
);
// Note: The two algorithms below are implemented in
// `encoding_rs::Decoder::decode_to_string_without_replacement`
//
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 5. While true:
// Step 5.1 Let item be the result of reading from thiss I/O queue.
// Step 5.2 If item is end-of-queue and thiss do not flush is true,
// then return the result of running serialize I/O queue with this and output.
// Step 5.3 Otherwise:
// Step 5.3.1 Let result be the result of processing an item with item, thiss decoder,
// thiss I/O queue, output, and thiss error mode.
//
// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
// Step 4. While true:
// Step 4.1 Let item be the result of reading from decoders 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.2 If outputChunk is not the empty string, then enqueue outputChunk in decoders transform.
// Step 4.2.3 Return.
// Step 4.3 Let result be the result of processing an item with item, decoders decoder,
// decoders I/O queue, output, and decoders error mode.
// Step 4.4 If result is error, then throw a TypeError.
let (result, read) =
decoder.decode_to_string_without_replacement(input, &mut output, last);
match result {
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 5.3.2 If result is finished, then return the result of running serialize I/O
// queue with this and output.
DecoderResult::InputEmpty => (output, read),
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 5.3.3 Otherwise, if result is error, throw a TypeError.
DecoderResult::Malformed(_, _) => {
return Err(Error::Type("Decoding failed".to_owned()));
},
DecoderResult::OutputFull => {
unreachable!("output is allocated with sufficient capacity")
},
}
} else {
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 4. Let output be the I/O queue of scalar values « end-of-queue ».
let mut output =
String::with_capacity(decoder.max_utf8_buffer_length(input.len()).ok_or_else(
|| Error::Type("Expected UTF8 buffer length would overflow".to_owned()),
)?);
// Note: The two algorithms below are implemented in
// `encoding_rs::Decoder::decode_to_string`
//
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 5. While true:
// Step 5.1 Let item be the result of reading from thiss I/O queue.
// Step 5.2 If item is end-of-queue and thiss do not flush is true,
// then return the result of running serialize I/O queue with this and output.
// Step 5.3 Otherwise:
// Step 5.3.1 Let result be the result of processing an item with item, thiss decoder,
// thiss I/O queue, output, and thiss error mode.
//
// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
// Step 4. While true:
// Step 4.1 Let item be the result of reading from decoders 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.2 If outputChunk is not the empty string, then enqueue outputChunk in decoders transform.
// Step 4.2.3 Return.
// Step 4.3 Let result be the result of processing an item with item, decoders decoder,
// decoders I/O queue, output, and decoders error mode.
// Step 4.4 If result is error, then throw a TypeError.
let (result, read, _replaced) = decoder.decode_to_string(input, &mut output, last);
match result {
// <https://encoding.spec.whatwg.org/#dom-textdecoder-decode>
// Step 5.3.2 If result is finished, then return the result of running serialize I/O
// queue with this and output.
encoding_rs::CoderResult::InputEmpty => (output, read),
encoding_rs::CoderResult::OutputFull => {
unreachable!("output is allocated with sufficient capacity")
},
}
};
let (_consumed, remaining) = input.split_at(read);
*io_queue = remaining.to_vec();
Ok(output)
}
}

View file

@ -0,0 +1,204 @@
/* 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};
/// <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk>
#[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 decoders 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 decoders 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, decoders decoder,
// decoders I/O queue, output, and decoders 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 decoders 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)
}
/// <https://encoding.spec.whatwg.org/#flush-and-enqueue>
#[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 decoders I/O queue.
// Step 2.2 Let result be the result of processing an item with item,
// decoders decoder, decoders I/O queue, output, and decoders 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 decoders 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)
}
/// <https://encoding.spec.whatwg.org/#textdecoderstream>
#[dom_struct]
pub(crate) struct TextDecoderStream {
reflector_: Reflector,
/// <https://encoding.spec.whatwg.org/#textdecodercommon>
#[ignore_malloc_size_of = "Rc is hard"]
decoder: Rc<TextDecoderCommon>,
/// <https://streams.spec.whatwg.org/#generictransformstream>
transform: Dom<TransformStream>,
}
#[allow(non_snake_case)]
impl TextDecoderStream {
fn new_inherited(
decoder: Rc<TextDecoderCommon>,
transform: &TransformStream,
) -> TextDecoderStream {
TextDecoderStream {
reflector_: Reflector::new(),
decoder,
transform: Dom::from_ref(transform),
}
}
fn new_with_proto(
cx: SafeJSContext,
global: &GlobalScope,
proto: Option<SafeHandleObject>,
encoding: &'static Encoding,
fatal: bool,
ignoreBOM: bool,
can_gc: CanGc,
) -> Fallible<DomRoot<Self>> {
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<crate::DomTypeHolder> for TextDecoderStream {
/// <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
fn Constructor(
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
label: DOMString,
options: &TextDecoderBinding::TextDecoderOptions,
) -> Fallible<DomRoot<TextDecoderStream>> {
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,
)
}
/// <https://encoding.spec.whatwg.org/#dom-textdecoder-encoding>
fn Encoding(&self) -> DOMString {
DOMString::from(self.decoder.encoding().name().to_ascii_lowercase())
}
/// <https://encoding.spec.whatwg.org/#dom-textdecoder-fatal>
fn Fatal(&self) -> bool {
self.decoder.fatal()
}
/// <https://encoding.spec.whatwg.org/#dom-textdecoder-ignorebom>
fn IgnoreBOM(&self) -> bool {
self.decoder.ignore_bom()
}
/// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable>
fn Readable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::ReadableStream> {
self.transform.get_readable()
}
/// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable>
fn Writable(&self) -> DomRoot<<crate::DomTypeHolder as DomTypes>::WritableStream> {
self.transform.get_writable()
}
}

View file

@ -36,6 +36,7 @@ use crate::dom::countqueuingstrategy::{extract_high_water_mark, extract_size_alg
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise; use crate::dom::promise::Promise;
use crate::dom::readablestream::{ReadableStream, create_readable_stream}; use crate::dom::readablestream::{ReadableStream, create_readable_stream};
use crate::dom::transformstreamdefaultcontroller::TransformerType;
use crate::dom::types::PromiseNativeHandler; use crate::dom::types::PromiseNativeHandler;
use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; use crate::dom::underlyingsourcecontainer::UnderlyingSourceType;
use crate::dom::writablestream::create_writable_stream; use crate::dom::writablestream::create_writable_stream;
@ -436,6 +437,60 @@ impl TransformStream {
) )
} }
/// Creates and set up the newly created transform stream following
/// <https://streams.spec.whatwg.org/#transformstream-set-up>
pub(crate) fn set_up(
&self,
cx: SafeJSContext,
global: &GlobalScope,
transformer_type: TransformerType,
can_gc: CanGc,
) -> Fallible<()> {
// Step1. Let writableHighWaterMark be 1.
let writable_high_water_mark = 1.0;
// Step 2. Let writableSizeAlgorithm be an algorithm that returns 1.
let writable_size_algorithm = extract_size_algorithm(&Default::default(), can_gc);
// Step 3. Let readableHighWaterMark be 0.
let readable_high_water_mark = 0.0;
// Step 4. Let readableSizeAlgorithm be an algorithm that returns 1.
let readable_size_algorithm = extract_size_algorithm(&Default::default(), can_gc);
// Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
// Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
// Step 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
// NOTE: These steps are implemented in `TransformStreamDefaultController::new`
// Step 8. Let startPromise be a promise resolved with undefined.
let start_promise = Promise::new_resolved(global, cx, (), can_gc);
// Step 9. Perform ! InitializeTransformStream(stream, startPromise,
// writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark,
// readableSizeAlgorithm).
self.initialize(
cx,
global,
start_promise.clone(),
writable_high_water_mark,
writable_size_algorithm,
readable_high_water_mark,
readable_size_algorithm,
can_gc,
)?;
// Step 10. Let controller be a new TransformStreamDefaultController.
let controller = TransformStreamDefaultController::new(global, transformer_type, can_gc);
// Step 11. Perform ! SetUpTransformStreamDefaultController(stream,
// controller, transformAlgorithmWrapper, flushAlgorithmWrapper,
// cancelAlgorithmWrapper).
self.set_up_transform_stream_default_controller(&controller);
Ok(())
}
pub(crate) fn get_controller(&self) -> DomRoot<TransformStreamDefaultController> { pub(crate) fn get_controller(&self) -> DomRoot<TransformStreamDefaultController> {
self.controller.get().expect("controller is not set") self.controller.get().expect("controller is not set")
} }
@ -568,7 +623,8 @@ impl TransformStream {
can_gc: CanGc, can_gc: CanGc,
) { ) {
// Let controller be a new TransformStreamDefaultController. // Let controller be a new TransformStreamDefaultController.
let controller = TransformStreamDefaultController::new(global, transformer, can_gc); let transformer_type = TransformerType::new_from_js_transformer(transformer);
let controller = TransformStreamDefaultController::new(global, transformer_type, can_gc);
// Let transformAlgorithm be the following steps, taking a chunk argument: // Let transformAlgorithm be the following steps, taking a chunk argument:
// Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk).
@ -892,6 +948,7 @@ impl TransformStream {
} }
} }
#[allow(non_snake_case)]
impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream { impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream {
/// <https://streams.spec.whatwg.org/#ts-constructor> /// <https://streams.spec.whatwg.org/#ts-constructor>
#[allow(unsafe_code)] #[allow(unsafe_code)]

View file

@ -14,17 +14,20 @@ use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}
use super::bindings::cell::DomRefCell; use super::bindings::cell::DomRefCell;
use super::bindings::codegen::Bindings::TransformerBinding::{ use super::bindings::codegen::Bindings::TransformerBinding::{
Transformer, TransformerCancelCallback, TransformerFlushCallback, TransformerTransformCallback, TransformerCancelCallback, TransformerFlushCallback, TransformerTransformCallback,
}; };
use super::types::TransformStream; use super::types::TransformStream;
use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::codegen::Bindings::TransformStreamDefaultControllerBinding::TransformStreamDefaultControllerMethods; use crate::dom::bindings::codegen::Bindings::TransformStreamDefaultControllerBinding::TransformStreamDefaultControllerMethods;
use crate::dom::bindings::codegen::Bindings::TransformerBinding::Transformer;
use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible}; use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise; use crate::dom::promise::Promise;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::textdecodercommon::TextDecoderCommon;
use crate::dom::textdecoderstream::{decode_and_enqueue_a_chunk, flush_and_enqueue};
use crate::realms::{InRealm, enter_realm}; use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
@ -51,26 +54,49 @@ impl Callback for TransformTransformPromiseRejection {
} }
} }
/// The type of transformer algorithms we are using
#[derive(JSTraceable)]
pub(crate) enum TransformerType {
/// Algorithms provided by Js callbacks
Js {
/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-cancelalgorithm>
cancel: RefCell<Option<Rc<TransformerCancelCallback>>>,
/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-flushalgorithm>
flush: RefCell<Option<Rc<TransformerFlushCallback>>>,
/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-transformalgorithm>
transform: RefCell<Option<Rc<TransformerTransformCallback>>>,
/// The JS object used as `this` when invoking sink algorithms.
transform_obj: Heap<*mut JSObject>,
},
/// Algorithms supporting `TextDecoderStream` are implemented in Rust
///
/// <https://encoding.spec.whatwg.org/#textdecodercommon>
Decoder(Rc<TextDecoderCommon>),
}
impl TransformerType {
pub(crate) fn new_from_js_transformer(transformer: &Transformer) -> TransformerType {
TransformerType::Js {
cancel: RefCell::new(transformer.cancel.clone()),
flush: RefCell::new(transformer.flush.clone()),
transform: RefCell::new(transformer.transform.clone()),
transform_obj: Default::default(),
}
}
}
/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller> /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller>
#[dom_struct] #[dom_struct]
pub struct TransformStreamDefaultController { pub struct TransformStreamDefaultController {
reflector_: Reflector, reflector_: Reflector,
/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-cancelalgorithm> /// The type of the underlying transformer used. Besides the JS variant,
#[ignore_malloc_size_of = "Rc is hard"] /// there will be other variant(s) for `TextDecoderStream`
cancel: RefCell<Option<Rc<TransformerCancelCallback>>>, #[ignore_malloc_size_of = "transformer_type"]
transformer_type: TransformerType,
/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-flushalgorithm>
#[ignore_malloc_size_of = "Rc is hard"]
flush: RefCell<Option<Rc<TransformerFlushCallback>>>,
/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-transformalgorithm>
#[ignore_malloc_size_of = "Rc is hard"]
transform: RefCell<Option<Rc<TransformerTransformCallback>>>,
/// The JS object used as `this` when invoking sink algorithms.
#[ignore_malloc_size_of = "mozjs"]
transform_obj: Heap<*mut JSObject>,
/// <https://streams.spec.whatwg.org/#TransformStreamDefaultController-stream> /// <https://streams.spec.whatwg.org/#TransformStreamDefaultController-stream>
stream: MutNullableDom<TransformStream>, stream: MutNullableDom<TransformStream>,
@ -82,34 +108,39 @@ pub struct TransformStreamDefaultController {
impl TransformStreamDefaultController { impl TransformStreamDefaultController {
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
fn new_inherited(transformer: &Transformer) -> TransformStreamDefaultController { fn new_inherited(transformer_type: TransformerType) -> TransformStreamDefaultController {
TransformStreamDefaultController { TransformStreamDefaultController {
reflector_: Reflector::new(), reflector_: Reflector::new(),
cancel: RefCell::new(transformer.cancel.clone()), transformer_type,
flush: RefCell::new(transformer.flush.clone()),
transform: RefCell::new(transformer.transform.clone()),
finish_promise: DomRefCell::new(None),
stream: MutNullableDom::new(None), stream: MutNullableDom::new(None),
transform_obj: Default::default(), finish_promise: DomRefCell::new(None),
} }
} }
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new( pub(crate) fn new(
global: &GlobalScope, global: &GlobalScope,
transformer: &Transformer, transformer_type: TransformerType,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<TransformStreamDefaultController> { ) -> DomRoot<TransformStreamDefaultController> {
reflect_dom_object( reflect_dom_object(
Box::new(TransformStreamDefaultController::new_inherited(transformer)), Box::new(TransformStreamDefaultController::new_inherited(
transformer_type,
)),
global, global,
can_gc, can_gc,
) )
} }
/// Setting the JS object after the heap has settled down. /// Setting the JS object after the heap has settled down.
///
/// Note that this has no effect if the transformer type is not `TransformerType::Js`
pub(crate) fn set_transform_obj(&self, this_object: SafeHandleObject) { pub(crate) fn set_transform_obj(&self, this_object: SafeHandleObject) {
self.transform_obj.set(*this_object); if let TransformerType::Js { transform_obj, .. } = &self.transformer_type {
transform_obj.set(*this_object)
} else {
unreachable!("Non-Js transformer type should not set transform_obj")
}
} }
pub(crate) fn set_stream(&self, stream: &TransformStream) { pub(crate) fn set_stream(&self, stream: &TransformStream) {
@ -160,41 +191,71 @@ impl TransformStreamDefaultController {
chunk: SafeHandleValue, chunk: SafeHandleValue,
can_gc: CanGc, can_gc: CanGc,
) -> Fallible<Rc<Promise>> { ) -> Fallible<Rc<Promise>> {
// If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which let result = match &self.transformer_type {
// takes an argument chunk and returns the result of invoking transformerDict["transform"] with argument list // <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer>
// « chunk, controller » and callback this value transformer. TransformerType::Js {
let algo = self.transform.borrow().clone(); transform,
let result = if let Some(transform) = algo { transform_obj,
rooted!(in(*cx) let this_object = self.transform_obj.get()); ..
let call_result = transform.Call_( } => {
&this_object.handle(), // Step 5. If transformerDict["transform"] exists, set
chunk, // transformAlgorithm to an algorithm which takes an argument
self, // chunk and returns the result of invoking
ExceptionHandling::Rethrow, // transformerDict["transform"] with argument list « chunk,
can_gc, // controller » and callback this value transformer.
); let algo = transform.borrow().clone();
match call_result { if let Some(transform) = algo {
Ok(p) => p, rooted!(in(*cx) let this_object = transform_obj.get());
Err(e) => { transform
let p = Promise::new(global, can_gc); .Call_(
p.reject_error(e, can_gc); &this_object.handle(),
p chunk,
}, self,
} ExceptionHandling::Rethrow,
} else { can_gc,
// Let transformAlgorithm be the following steps, taking a chunk argument: )
// Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). .unwrap_or_else(|e| {
// If result is an abrupt completion, return a promise rejected with result.[[Value]]. let p = Promise::new(global, can_gc);
let promise = if let Err(error) = self.enqueue(cx, global, chunk, can_gc) { p.reject_error(e, can_gc);
rooted!(in(*cx) let mut error_val = UndefinedValue()); p
error.to_jsval(cx, global, error_val.handle_mut(), can_gc); })
Promise::new_rejected(global, cx, error_val.handle(), can_gc) } else {
} else { // Step 2. Let transformAlgorithm be the following steps, taking a chunk argument:
// Otherwise, return a promise resolved with undefined. // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk).
Promise::new_resolved(global, cx, (), can_gc) // If result is an abrupt completion, return a promise rejected with result.[[Value]].
}; if let Err(error) = self.enqueue(cx, global, chunk, can_gc) {
rooted!(in(*cx) let mut error_val = UndefinedValue());
promise error.to_jsval(cx, global, error_val.handle_mut(), can_gc);
Promise::new_rejected(global, cx, error_val.handle(), can_gc)
} else {
// Otherwise, return a promise resolved with undefined.
Promise::new_resolved(global, cx, (), can_gc)
}
}
},
TransformerType::Decoder(decoder) => {
// <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
// Step 7. Let transformAlgorithm be an algorithm which takes a
// chunk argument and runs the decode and enqueue a chunk
// algorithm with this and chunk.
decode_and_enqueue_a_chunk(cx, global, chunk, decoder, self, can_gc)
// <https://streams.spec.whatwg.org/#transformstream-set-up>
// Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps given a value chunk:
// Step 5.1 Let result be the result of running transformAlgorithm given chunk.
// Step 5.2 If result is a Promise, then return result.
// Note: not applicable, the spec does NOT require deode_and_enqueue_a_chunk() to return a Promise
// Step 5.3 Return a promise resolved with undefined.
.map(|_| Promise::new_resolved(global, cx, (), can_gc))
.unwrap_or_else(|e| {
// <https://streams.spec.whatwg.org/#transformstream-set-up>
// Step 5.1 If this throws an exception e,
let realm = enter_realm(self);
let p = Promise::new_in_current_realm((&realm).into(), can_gc);
// return a promise rejected with e.
p.reject_error(e, can_gc);
p
})
},
}; };
Ok(result) Ok(result)
@ -207,29 +268,49 @@ impl TransformStreamDefaultController {
chunk: SafeHandleValue, chunk: SafeHandleValue,
can_gc: CanGc, can_gc: CanGc,
) -> Fallible<Rc<Promise>> { ) -> Fallible<Rc<Promise>> {
// If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument let result = match &self.transformer_type {
// reason and returns the result of invoking transformerDict["cancel"] with argument list « reason » // <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer>
// and callback this value transformer. TransformerType::Js {
let algo = self.cancel.borrow().clone(); cancel,
let result = if let Some(cancel) = algo { transform_obj,
rooted!(in(*cx) let this_object = self.transform_obj.get()); ..
let call_result = cancel.Call_( } => {
&this_object.handle(), // Step 7. If transformerDict["cancel"] exists, set
chunk, // cancelAlgorithm to an algorithm which takes an argument
ExceptionHandling::Rethrow, // reason and returns the result of invoking
can_gc, // transformerDict["cancel"] with argument list « reason » and
); // callback this value transformer.
match call_result { let algo = cancel.borrow().clone();
Ok(p) => p, if let Some(cancel) = algo {
Err(e) => { rooted!(in(*cx) let this_object = transform_obj.get());
let p = Promise::new(global, can_gc); cancel
p.reject_error(e, can_gc); .Call_(
p &this_object.handle(),
}, chunk,
} ExceptionHandling::Rethrow,
} else { can_gc,
// Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. )
Promise::new_resolved(global, cx, (), can_gc) .unwrap_or_else(|e| {
let p = Promise::new(global, can_gc);
p.reject_error(e, can_gc);
p
})
} else {
// Step 4. Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined.
Promise::new_resolved(global, cx, (), can_gc)
}
},
TransformerType::Decoder(_) => {
// <https://streams.spec.whatwg.org/#transformstream-set-up>
// Step 7. Let cancelAlgorithmWrapper be an algorithm that runs these steps given a value reason:
// Step 7.1 Let result be the result of running cancelAlgorithm given reason,
// if cancelAlgorithm was given, or null otherwise
// Note: `TextDecoderStream` does NOT specify a cancel algorithm.
// Step 7.2 If result is a Promise, then return result.
// Note: Not applicable.
// Step 7.3 Return a promise resolved with undefined.
Promise::new_resolved(global, cx, (), can_gc)
},
}; };
Ok(result) Ok(result)
@ -241,28 +322,60 @@ impl TransformStreamDefaultController {
global: &GlobalScope, global: &GlobalScope,
can_gc: CanGc, can_gc: CanGc,
) -> Fallible<Rc<Promise>> { ) -> Fallible<Rc<Promise>> {
// If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns the result of let result = match &self.transformer_type {
// invoking transformerDict["flush"] with argument list « controller » and callback this value transformer. // <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer>
let algo = self.flush.borrow().clone(); TransformerType::Js {
let result = if let Some(flush) = algo { flush,
rooted!(in(*cx) let this_object = self.transform_obj.get()); transform_obj,
let call_result = flush.Call_( ..
&this_object.handle(), } => {
self, // Step 6. If transformerDict["flush"] exists, set flushAlgorithm to an
ExceptionHandling::Rethrow, // algorithm which returns the result of invoking
can_gc, // transformerDict["flush"] with argument list « controller »
); // and callback this value transformer.
match call_result { let algo = flush.borrow().clone();
Ok(p) => p, if let Some(flush) = algo {
Err(e) => { rooted!(in(*cx) let this_object = transform_obj.get());
let p = Promise::new(global, can_gc); flush
p.reject_error(e, can_gc); .Call_(
p &this_object.handle(),
}, self,
} ExceptionHandling::Rethrow,
} else { can_gc,
// Let flushAlgorithm be an algorithm which returns a promise resolved with undefined. )
Promise::new_resolved(global, cx, (), can_gc) .unwrap_or_else(|e| {
let p = Promise::new(global, can_gc);
p.reject_error(e, can_gc);
p
})
} else {
// Step 3. Let flushAlgorithm be an algorithm which returns a promise resolved with undefined.
Promise::new_resolved(global, cx, (), can_gc)
}
},
TransformerType::Decoder(decoder) => {
// <https://encoding.spec.whatwg.org/#dom-textdecoderstream>
// Step 8. Let flushAlgorithm be an algorithm which takes no
// arguments and runs the flush and enqueue algorithm with this.
flush_and_enqueue(cx, global, decoder, self, can_gc)
// <https://streams.spec.whatwg.org/#transformstream-set-up>
// Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
// Step 6.1 Let result be the result of running flushAlgorithm,
// if flushAlgorithm was given, or null otherwise.
// Step 6.2 If result is a Promise, then return result.
// Note: Not applicable. The spec does NOT require flush_and_enqueue algo to return a Promise
// Step 6.3 Return a promise resolved with undefined.
.map(|_| Promise::new_resolved(global, cx, (), can_gc))
.unwrap_or_else(|e| {
// <https://streams.spec.whatwg.org/#transformstream-set-up>
// Step 6.1 If this throws an exception e,
let realm = enter_realm(self);
let p = Promise::new_in_current_realm((&realm).into(), can_gc);
// return a promise rejected with e.
p.reject_error(e, can_gc);
p
})
},
}; };
Ok(result) Ok(result)
@ -349,14 +462,22 @@ impl TransformStreamDefaultController {
/// <https://streams.spec.whatwg.org/#transform-stream-default-controller-clear-algorithms> /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-clear-algorithms>
pub(crate) fn clear_algorithms(&self) { pub(crate) fn clear_algorithms(&self) {
// Set controller.[[transformAlgorithm]] to undefined. if let TransformerType::Js {
self.transform.replace(None); cancel,
flush,
transform,
..
} = &self.transformer_type
{
// Set controller.[[transformAlgorithm]] to undefined.
transform.replace(None);
// Set controller.[[flushAlgorithm]] to undefined. // Set controller.[[flushAlgorithm]] to undefined.
self.flush.replace(None); flush.replace(None);
// Set controller.[[cancelAlgorithm]] to undefined. // Set controller.[[cancelAlgorithm]] to undefined.
self.cancel.replace(None); cancel.replace(None);
}
} }
/// <https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate> /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate>
@ -381,6 +502,7 @@ impl TransformStreamDefaultController {
} }
} }
#[allow(non_snake_case)]
impl TransformStreamDefaultControllerMethods<crate::DomTypeHolder> impl TransformStreamDefaultControllerMethods<crate::DomTypeHolder>
for TransformStreamDefaultController for TransformStreamDefaultController
{ {

View file

@ -0,0 +1,9 @@
/* 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/. */
// https://streams.spec.whatwg.org/#generictransformstream
interface mixin GenericTransformStream {
readonly attribute ReadableStream readable;
readonly attribute WritableStream writable;
};

View file

@ -12,7 +12,7 @@ dictionary TextDecodeOptions {
boolean stream = false; boolean stream = false;
}; };
[Exposed=*] [Exposed=(Window,Worker)]
interface TextDecoder { interface TextDecoder {
[Throws] constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {}); [Throws] constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {});

View file

@ -0,0 +1,15 @@
/* 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/. */
/*
* For more information on this interface please see
* https://encoding.spec.whatwg.org/#textdecoderstream
*/
[Exposed=*]
interface TextDecoderStream {
[Throws] constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {});
};
TextDecoderStream includes TextDecoderCommon;
TextDecoderStream includes GenericTransformStream;

View file

@ -5,33 +5,6 @@
expected: ERROR expected: ERROR
[idlharness.any.html] [idlharness.any.html]
[TextDecoderStream interface: existence and properties of interface object]
expected: FAIL
[TextDecoderStream interface object length]
expected: FAIL
[TextDecoderStream interface object name]
expected: FAIL
[TextDecoderStream interface: existence and properties of interface prototype object]
expected: FAIL
[TextDecoderStream interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[TextDecoderStream interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[TextDecoderStream interface: attribute encoding]
expected: FAIL
[TextDecoderStream interface: attribute fatal]
expected: FAIL
[TextDecoderStream interface: attribute ignoreBOM]
expected: FAIL
[TextEncoderStream interface: existence and properties of interface object] [TextEncoderStream interface: existence and properties of interface object]
expected: FAIL expected: FAIL
@ -55,33 +28,6 @@
[idlharness.any.worker.html] [idlharness.any.worker.html]
[TextDecoderStream interface: existence and properties of interface object]
expected: FAIL
[TextDecoderStream interface object length]
expected: FAIL
[TextDecoderStream interface object name]
expected: FAIL
[TextDecoderStream interface: existence and properties of interface prototype object]
expected: FAIL
[TextDecoderStream interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[TextDecoderStream interface: existence and properties of interface prototype object's @@unscopables property]
expected: FAIL
[TextDecoderStream interface: attribute encoding]
expected: FAIL
[TextDecoderStream interface: attribute fatal]
expected: FAIL
[TextDecoderStream interface: attribute ignoreBOM]
expected: FAIL
[TextEncoderStream interface: existence and properties of interface object] [TextEncoderStream interface: existence and properties of interface object]
expected: FAIL expected: FAIL

View file

@ -1,8 +0,0 @@
[gb18030-decoder.any.worker.html]
[gb18030 decoder: two bytes 0xFE 0xFF]
expected: FAIL
[gb18030-decoder.any.html]
[gb18030 decoder: two bytes 0xFE 0xFF]
expected: FAIL

View file

@ -1,10 +1,4 @@
[backpressure.any.worker.html] [backpressure.any.worker.html]
[write() should not complete until read relieves backpressure for TextDecoderStream]
expected: FAIL
[additional writes should wait for backpressure to be relieved for class TextDecoderStream]
expected: FAIL
[write() should not complete until read relieves backpressure for TextEncoderStream] [write() should not complete until read relieves backpressure for TextEncoderStream]
expected: FAIL expected: FAIL
@ -19,12 +13,6 @@
expected: ERROR expected: ERROR
[backpressure.any.html] [backpressure.any.html]
[write() should not complete until read relieves backpressure for TextDecoderStream]
expected: FAIL
[additional writes should wait for backpressure to be relieved for class TextDecoderStream]
expected: FAIL
[write() should not complete until read relieves backpressure for TextEncoderStream] [write() should not complete until read relieves backpressure for TextEncoderStream]
expected: FAIL expected: FAIL

View file

@ -2,179 +2,11 @@
expected: ERROR expected: ERROR
[decode-attributes.any.html] [decode-attributes.any.html]
[encoding attribute should have correct value for 'unicode-1-1-utf-8']
expected: FAIL
[encoding attribute should have correct value for 'iso-8859-2']
expected: FAIL
[encoding attribute should have correct value for 'ascii']
expected: FAIL
[encoding attribute should have correct value for 'utf-16']
expected: FAIL
[setting fatal to 'false' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to 'false' should set the attribute to false]
expected: FAIL
[setting fatal to '0' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to '0' should set the attribute to false]
expected: FAIL
[setting fatal to '' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to '' should set the attribute to false]
expected: FAIL
[setting fatal to 'undefined' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to 'undefined' should set the attribute to false]
expected: FAIL
[setting fatal to 'null' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to 'null' should set the attribute to false]
expected: FAIL
[setting fatal to 'true' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to 'true' should set the attribute to true]
expected: FAIL
[setting fatal to '1' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to '1' should set the attribute to true]
expected: FAIL
[setting fatal to '[object Object\]' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to '[object Object\]' should set the attribute to true]
expected: FAIL
[setting fatal to '' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to '' should set the attribute to true]
expected: FAIL
[setting fatal to 'yes' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to 'yes' should set the attribute to true]
expected: FAIL
[constructing with an invalid encoding should throw]
expected: FAIL
[constructing with a non-stringifiable encoding should throw]
expected: FAIL
[a throwing fatal member should cause the constructor to throw]
expected: FAIL
[a throwing ignoreBOM member should cause the constructor to throw]
expected: FAIL
[decode-attributes.any.serviceworker.html] [decode-attributes.any.serviceworker.html]
expected: ERROR expected: ERROR
[decode-attributes.any.worker.html] [decode-attributes.any.worker.html]
[encoding attribute should have correct value for 'unicode-1-1-utf-8']
expected: FAIL
[encoding attribute should have correct value for 'iso-8859-2']
expected: FAIL
[encoding attribute should have correct value for 'ascii']
expected: FAIL
[encoding attribute should have correct value for 'utf-16']
expected: FAIL
[setting fatal to 'false' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to 'false' should set the attribute to false]
expected: FAIL
[setting fatal to '0' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to '0' should set the attribute to false]
expected: FAIL
[setting fatal to '' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to '' should set the attribute to false]
expected: FAIL
[setting fatal to 'undefined' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to 'undefined' should set the attribute to false]
expected: FAIL
[setting fatal to 'null' should set the attribute to false]
expected: FAIL
[setting ignoreBOM to 'null' should set the attribute to false]
expected: FAIL
[setting fatal to 'true' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to 'true' should set the attribute to true]
expected: FAIL
[setting fatal to '1' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to '1' should set the attribute to true]
expected: FAIL
[setting fatal to '[object Object\]' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to '[object Object\]' should set the attribute to true]
expected: FAIL
[setting fatal to '' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to '' should set the attribute to true]
expected: FAIL
[setting fatal to 'yes' should set the attribute to true]
expected: FAIL
[setting ignoreBOM to 'yes' should set the attribute to true]
expected: FAIL
[constructing with an invalid encoding should throw]
expected: FAIL
[constructing with a non-stringifiable encoding should throw]
expected: FAIL
[a throwing fatal member should cause the constructor to throw]
expected: FAIL
[a throwing ignoreBOM member should cause the constructor to throw]
expected: FAIL
[decode-attributes.any.shadowrealm.html] [decode-attributes.any.shadowrealm.html]
expected: TIMEOUT expected: TIMEOUT

View file

@ -1,36 +1,6 @@
[decode-bad-chunks.any.html] [decode-bad-chunks.any.html]
[chunk of type undefined should error the stream]
expected: FAIL
[chunk of type null should error the stream]
expected: FAIL
[chunk of type numeric should error the stream]
expected: FAIL
[chunk of type object, not BufferSource should error the stream]
expected: FAIL
[chunk of type array should error the stream]
expected: FAIL
[decode-bad-chunks.any.worker.html] [decode-bad-chunks.any.worker.html]
[chunk of type undefined should error the stream]
expected: FAIL
[chunk of type null should error the stream]
expected: FAIL
[chunk of type numeric should error the stream]
expected: FAIL
[chunk of type object, not BufferSource should error the stream]
expected: FAIL
[chunk of type array should error the stream]
expected: FAIL
[decode-bad-chunks.any.sharedworker.html] [decode-bad-chunks.any.sharedworker.html]
expected: ERROR expected: ERROR

View file

@ -2,79 +2,8 @@
expected: ERROR expected: ERROR
[decode-ignore-bom.any.html] [decode-ignore-bom.any.html]
[ignoreBOM should work for encoding utf-8, split at character 0]
expected: FAIL
[ignoreBOM should work for encoding utf-8, split at character 1]
expected: FAIL
[ignoreBOM should work for encoding utf-8, split at character 2]
expected: FAIL
[ignoreBOM should work for encoding utf-8, split at character 3]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 0]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 1]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 2]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 3]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 0]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 1]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 2]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 3]
expected: FAIL
[decode-ignore-bom.any.serviceworker.html] [decode-ignore-bom.any.serviceworker.html]
expected: ERROR expected: ERROR
[decode-ignore-bom.any.worker.html] [decode-ignore-bom.any.worker.html]
[ignoreBOM should work for encoding utf-8, split at character 0]
expected: FAIL
[ignoreBOM should work for encoding utf-8, split at character 1]
expected: FAIL
[ignoreBOM should work for encoding utf-8, split at character 2]
expected: FAIL
[ignoreBOM should work for encoding utf-8, split at character 3]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 0]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 1]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 2]
expected: FAIL
[ignoreBOM should work for encoding utf-16le, split at character 3]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 0]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 1]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 2]
expected: FAIL
[ignoreBOM should work for encoding utf-16be, split at character 3]
expected: FAIL

View file

@ -1,21 +1,9 @@
[decode-incomplete-input.any.html] [decode-incomplete-input.any.html]
[incomplete input with error mode "replacement" should end with a replacement character]
expected: FAIL
[incomplete input with error mode "fatal" should error the stream]
expected: FAIL
[decode-incomplete-input.any.sharedworker.html] [decode-incomplete-input.any.sharedworker.html]
expected: ERROR expected: ERROR
[decode-incomplete-input.any.worker.html] [decode-incomplete-input.any.worker.html]
[incomplete input with error mode "replacement" should end with a replacement character]
expected: FAIL
[incomplete input with error mode "fatal" should error the stream]
expected: FAIL
[decode-incomplete-input.any.serviceworker.html] [decode-incomplete-input.any.serviceworker.html]
expected: ERROR expected: ERROR

View file

@ -2,86 +2,8 @@
expected: ERROR expected: ERROR
[decode-non-utf8.any.worker.html] [decode-non-utf8.any.worker.html]
[TextDecoderStream should be able to reject invalid sequences in Shift_JIS]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in Shift_JIS]
expected: FAIL
[TextDecoderStream should be able to reject invalid sequences in UTF-16LE]
expected: FAIL
[TextDecoderStream should be able to reject invalid sequences in UTF-16BE]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in ISO-2022-JP]
expected: FAIL
[TextDecoderStream should be able to decode ISO-2022-JP]
expected: FAIL
[TextDecoderStream should be able to reject invalid sequences in ISO-2022-JP]
expected: FAIL
[TextDecoderStream should be able to decode Shift_JIS]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in UTF-16LE]
expected: FAIL
[TextDecoderStream should be able to decode UTF-16BE]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in UTF-16BE]
expected: FAIL
[TextDecoderStream should be able to decode UTF-16LE]
expected: FAIL
[TextDecoderStream should be able to decode ISO-8859-14]
expected: FAIL
[decode-non-utf8.any.html] [decode-non-utf8.any.html]
[TextDecoderStream should be able to reject invalid sequences in Shift_JIS]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in Shift_JIS]
expected: FAIL
[TextDecoderStream should be able to reject invalid sequences in UTF-16LE]
expected: FAIL
[TextDecoderStream should be able to reject invalid sequences in UTF-16BE]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in ISO-2022-JP]
expected: FAIL
[TextDecoderStream should be able to decode ISO-2022-JP]
expected: FAIL
[TextDecoderStream should be able to reject invalid sequences in ISO-2022-JP]
expected: FAIL
[TextDecoderStream should be able to decode Shift_JIS]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in UTF-16LE]
expected: FAIL
[TextDecoderStream should be able to decode UTF-16BE]
expected: FAIL
[TextDecoderStream should be able to decode invalid sequences in UTF-16BE]
expected: FAIL
[TextDecoderStream should be able to decode UTF-16LE]
expected: FAIL
[TextDecoderStream should be able to decode ISO-8859-14]
expected: FAIL
[decode-non-utf8.any.serviceworker.html] [decode-non-utf8.any.serviceworker.html]
expected: ERROR expected: ERROR

View file

@ -5,70 +5,5 @@
expected: ERROR expected: ERROR
[decode-split-character.any.worker.html] [decode-split-character.any.worker.html]
[a code point split between chunks should not be emitted until all bytes are available; split point = 2]
expected: FAIL
[a code point split between chunks should not be emitted until all bytes are available; split point = 3]
expected: FAIL
[a code point split between chunks should not be emitted until all bytes are available; split point = 4]
expected: FAIL
[a code point split between chunks should not be emitted until all bytes are available; split point = 5]
expected: FAIL
[a code point should be emitted as soon as all bytes are available]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 1]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 2]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 3]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 4]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 5]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 6]
expected: FAIL
[decode-split-character.any.html] [decode-split-character.any.html]
[a code point split between chunks should not be emitted until all bytes are available; split point = 2]
expected: FAIL
[a code point split between chunks should not be emitted until all bytes are available; split point = 3]
expected: FAIL
[a code point split between chunks should not be emitted until all bytes are available; split point = 4]
expected: FAIL
[a code point split between chunks should not be emitted until all bytes are available; split point = 5]
expected: FAIL
[a code point should be emitted as soon as all bytes are available]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 1]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 2]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 3]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 4]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 5]
expected: FAIL
[an empty chunk inside a code point split between chunks should not change the output; split point = 6]
expected: FAIL

View file

@ -6,35 +6,6 @@
[decode-utf8.any.worker.html] [decode-utf8.any.worker.html]
expected: ERROR expected: ERROR
[decoding one UTF-8 chunk should give one output string - ArrayBuffer]
expected: FAIL
[decoding an empty chunk should give no output chunks - ArrayBuffer]
expected: FAIL
[an initial empty chunk should be ignored - ArrayBuffer]
expected: FAIL
[a trailing empty chunk should be ignored - ArrayBuffer]
expected: FAIL
[UTF-8 EOF handling - ArrayBuffer]
expected: FAIL
[decode-utf8.any.html] [decode-utf8.any.html]
expected: ERROR expected: ERROR
[decoding one UTF-8 chunk should give one output string - ArrayBuffer]
expected: FAIL
[decoding an empty chunk should give no output chunks - ArrayBuffer]
expected: FAIL
[an initial empty chunk should be ignored - ArrayBuffer]
expected: FAIL
[a trailing empty chunk should be ignored - ArrayBuffer]
expected: FAIL
[UTF-8 EOF handling - ArrayBuffer]
expected: FAIL

View file

@ -1,12 +1,6 @@
[invalid-realm.window.html] [invalid-realm.window.html]
[TextDecoderStream: write in detached realm should succeed]
expected: FAIL
[TextEncoderStream: write in detached realm should succeed] [TextEncoderStream: write in detached realm should succeed]
expected: FAIL expected: FAIL
[TextEncoderStream: close in detached realm should succeed] [TextEncoderStream: close in detached realm should succeed]
expected: FAIL expected: FAIL
[TextDecoderStream: close in detached realm should succeed]
expected: FAIL

View file

@ -2,9 +2,6 @@
[TextEncoderStream readable and writable properties must pass brand checks] [TextEncoderStream readable and writable properties must pass brand checks]
expected: FAIL expected: FAIL
[TextDecoderStream readable and writable properties must pass brand checks]
expected: FAIL
[readable-writable-properties.any.sharedworker.html] [readable-writable-properties.any.sharedworker.html]
expected: ERROR expected: ERROR
@ -16,9 +13,6 @@
[TextEncoderStream readable and writable properties must pass brand checks] [TextEncoderStream readable and writable properties must pass brand checks]
expected: FAIL expected: FAIL
[TextDecoderStream readable and writable properties must pass brand checks]
expected: FAIL
[readable-writable-properties.any.shadowrealm.html] [readable-writable-properties.any.shadowrealm.html]
expected: TIMEOUT expected: TIMEOUT

View file

@ -13,24 +13,3 @@
[TypeError for unconvertable chunk should come from constructor realm of TextEncoderStream] [TypeError for unconvertable chunk should come from constructor realm of TextEncoderStream]
expected: FAIL expected: FAIL
[a TextDecoderStream object should be associated with the realm the constructor came from]
expected: FAIL
[TextDecoderStream's readable and writable attributes should come from the same realm as the constructor definition]
expected: FAIL
[the result object when read is called after write should come from the same realm as the constructor of TextDecoderStream]
expected: FAIL
[the result object when write is called with a pending read should come from the same realm as the constructor of TextDecoderStream]
expected: FAIL
[TypeError for chunk with the wrong type should come from constructor realm of TextDecoderStream]
expected: FAIL
[TypeError for invalid chunk should come from constructor realm of TextDecoderStream]
expected: FAIL
[TypeError for incomplete input should come from constructor realm of TextDecoderStream]
expected: FAIL

View file

@ -13725,14 +13725,14 @@
] ]
], ],
"interfaces.https.html": [ "interfaces.https.html": [
"641c5ba19d389390b7b51da7644f011b0c42f33a", "efb780c382456b1fda514b33fe5b317c447fa09e",
[ [
null, null,
{} {}
] ]
], ],
"interfaces.worker.js": [ "interfaces.worker.js": [
"463bfc25211014203a5094baae4b4e2d890bf9aa", "a02605ff8e63db701c77d734037fcb33d76418d0",
[ [
"mozilla/interfaces.worker.html", "mozilla/interfaces.worker.html",
{} {}

View file

@ -326,6 +326,7 @@ test_interfaces([
"TextTrackCueList", "TextTrackCueList",
"TextTrackList", "TextTrackList",
"TextDecoder", "TextDecoder",
"TextDecoderStream",
"TextEncoder", "TextEncoder",
"TimeRanges", "TimeRanges",
"Touch", "Touch",

View file

@ -110,6 +110,7 @@ test_interfaces([
"SecurityPolicyViolationEvent", "SecurityPolicyViolationEvent",
"ServiceWorkerContainer", "ServiceWorkerContainer",
"TextDecoder", "TextDecoder",
"TextDecoderStream",
"TextEncoder", "TextEncoder",
"TrustedHTML", "TrustedHTML",
"TrustedScript", "TrustedScript",