/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/stream/decompressionstream.rs
364 строки
14 KB
Gae24
script: Drop unsafe `ToJSValConvertible::to_jsval` and wrapper trait `SafeToJSValConvertible` (#46890)
31 июл 2026, 10:20
Не верифицирован
31 июл 2026, 10:20
559e0a5
Код
Авторство
О чём код?
/* 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 http://mozilla.org/MPL/2.0/. */ use std::cell::RefCell; use std::io::{self, Write}; use std::ptr; use brotli::DecompressorWriter as BrotliDecoder; use dom_struct::dom_struct; use flate2::write::{DeflateDecoder, GzDecoder, ZlibDecoder}; use js::jsapi::JSObject; use js::jsval::UndefinedValue; use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}; use js::typedarray::Uint8; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::CompressionFormat; use crate::dom::bindings::codegen::Bindings::DecompressionStreamBinding::DecompressionStreamMethods; use crate::dom::bindings::conversions::ToJSValConvertible; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::stream::compressionstream::{BROTLI_BUFFER_SIZE, convert_chunk_to_vec}; use crate::dom::stream::transformstreamdefaultcontroller::TransformerType; use crate::dom::types::{ GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream, }; /// <https://compression.spec.whatwg.org/#decompressionstream> #[dom_struct] pub(crate) struct DecompressionStream { reflector_: Reflector, /// <https://streams.spec.whatwg.org/#generictransformstream> transform: Dom<TransformStream>, /// <https://compression.spec.whatwg.org/#decompressionstream-format> format: CompressionFormat, // <https://compression.spec.whatwg.org/#decompressionstream-context> #[no_trace] context: RefCell<DecompressionContext>, } impl DecompressionStream { fn new_inherited( transform: &TransformStream, format: CompressionFormat, ) -> DecompressionStream { DecompressionStream { reflector_: Reflector::new(), transform: Dom::from_ref(transform), format, context: RefCell::new(DecompressionContext::new(format)), } } fn new_with_proto( cx: &mut js::context::JSContext, global: &GlobalScope, proto: Option<SafeHandleObject>, transform: &TransformStream, format: CompressionFormat, ) -> DomRoot<DecompressionStream> { reflect_dom_object_with_proto( cx, Box::new(DecompressionStream::new_inherited(transform, format)), global, proto, ) } } impl DecompressionStreamMethods<crate::DomTypeHolder> for DecompressionStream { /// <https://compression.spec.whatwg.org/#dom-decompressionstream-decompressionstream> fn Constructor( cx: &mut js::context::JSContext, global: &GlobalScope, proto: Option<SafeHandleObject>, format: CompressionFormat, ) -> Fallible<DomRoot<DecompressionStream>> { // Step 1. If format is unsupported in DecompressionStream, then throw a TypeError. // NOTE: All of "brotli", "deflate", "deflate-raw" and "gzip" are supported. // Step 2. Set this’s format to format. // Step 5. Set this’s transform to a new TransformStream. let transform = TransformStream::new_with_proto(cx, global, None); let decompression_stream = DecompressionStream::new_with_proto(cx, global, proto, &transform, format); // Step 3. Let transformAlgorithm be an algorithm which takes a chunk argument and runs the // decompress and enqueue a chunk algorithm with this and chunk. // Step 4. Let flushAlgorithm be an algorithm which takes no argument and runs the // decompress flush and enqueue algorithm with this. // Step 6. Set up this’s transform with transformAlgorithm set to transformAlgorithm and // flushAlgorithm set to flushAlgorithm. transform.set_up( cx, global, TransformerType::Decompressor(decompression_stream.as_traced()), )?; Ok(decompression_stream) } /// <https://streams.spec.whatwg.org/#dom-generictransformstream-readable> fn Readable(&self) -> DomRoot<ReadableStream> { // The readable getter steps are to return this’s transform.[[readable]]. self.transform.get_readable() } /// <https://streams.spec.whatwg.org/#dom-generictransformstream-writable> fn Writable(&self) -> DomRoot<WritableStream> { // The writable getter steps are to return this’s transform.[[writable]]. self.transform.get_writable() } } /// <https://compression.spec.whatwg.org/#decompress-and-enqueue-a-chunk> pub(crate) fn decompress_and_enqueue_a_chunk( cx: &mut js::context::JSContext, global: &GlobalScope, ds: &DecompressionStream, chunk: SafeHandleValue, controller: &TransformStreamDefaultController, ) -> Fallible<()> { // Step 1. If chunk is not a BufferSource type, then throw a TypeError. let chunk = convert_chunk_to_vec(cx, chunk)?; // Step 2. Let buffer be the result of decompressing chunk with ds’s format and context. If // this results in an error, then throw a TypeError. // NOTE: In our implementation, the enum type of context already indicates the format. let buffer = { let mut decompression_context = ds.context.borrow_mut(); let buffer = decompression_context .decompress(&chunk) .map_err(|_| Error::Type(c"Failed to decompress a chunk of compressed input".into()))?; // Step 3. If buffer is empty, return. if buffer.is_empty() { return Ok(()); } buffer }; // Step 4. Let arrays be the result of splitting buffer into one or more non-empty pieces and // converting them into Uint8Arrays. // Step 5. For each Uint8Array array of arrays, enqueue array in ds’s transform. // NOTE: We process the result in a single Uint8Array. rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>()); let array = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut()) .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?; rooted!(&in(cx) let mut rval = UndefinedValue()); array.safe_to_jsval(cx, rval.handle_mut()); controller.enqueue(cx, global, rval.handle())?; // Step 6. If the end of the compressed input has been reached, and ds’s context has not fully // consumed chunk, then throw a TypeError. if ds.context.borrow().is_ended { return Err(Error::Type( c"The end of the compressed input has been reached".to_owned(), )); } Ok(()) } /// <https://compression.spec.whatwg.org/#decompress-flush-and-enqueue> pub(crate) fn decompress_flush_and_enqueue( cx: &mut js::context::JSContext, global: &GlobalScope, ds: &DecompressionStream, controller: &TransformStreamDefaultController, ) -> Fallible<()> { // Step 1. Let buffer be the result of decompressing an empty input with ds’s format and // context, with the finish flag. // NOTE: In our implementation, the enum type of context already indicates the format. let buffer = { let mut decompression_context = ds.context.borrow_mut(); decompression_context .finalize() .map_err(|_| Error::Type(c"Failed to finalize the decompression stream".into()))? }; // Step 2. If buffer is empty, return. if !buffer.is_empty() { // Step 2.1. Let arrays be the result of splitting buffer into one or more non-empty pieces // and converting them into Uint8Arrays. // Step 2.2. For each Uint8Array array of arrays, enqueue array in ds’s transform. // NOTE: We process the result in a single Uint8Array. rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>()); let array = create_buffer_source::<Uint8>(cx, &buffer, js_object.handle_mut()) .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?; rooted!(&in(cx) let mut rval = UndefinedValue()); array.safe_to_jsval(cx, rval.handle_mut()); controller.enqueue(cx, global, rval.handle())?; } // Step 3. If the end of the compressed input has not been reached, then throw a TypeError. // // NOTE: If the end of the compressed input has not been reached, flate2::write::DeflateDecoder // and flate2::write::GzDecoder can detect it and throw an error on `try_finish` in Step 1. // However, flate2::write::ZlibDecoder does not. We need to test it by ourselves. // // To test it, we write one more byte to the decoder. If it accepts the extra byte, this // indicates the end has not been reached. Otherwise, the end has been reached. This test has // to been done before calling `try_finish`, so we execute it in Step 1, and store the result // in `is_ended`. if !ds.context.borrow().is_ended { return Err(Error::Type( c"The end of the compressed input has not been reached".to_owned(), )); } Ok(()) } /// An enum grouping decoders of differenct compression algorithms. enum Decoder { Brotli(Box<BrotliDecoder<Vec<u8>>>), Deflate(ZlibDecoder<Vec<u8>>), DeflateRaw(DeflateDecoder<Vec<u8>>), Gzip(GzDecoder<Vec<u8>>), } impl MallocSizeOf for Decoder { #[expect(unsafe_code)] fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { match self { Decoder::Brotli(decoder) => unsafe { ops.malloc_size_of(&**decoder) }, Decoder::Deflate(decoder) => decoder.size_of(ops), Decoder::DeflateRaw(decoder) => decoder.size_of(ops), Decoder::Gzip(decoder) => decoder.size_of(ops), } } } /// <https://compression.spec.whatwg.org/#decompressionstream-context> /// Used to encapsulate the logic of decoder. #[derive(MallocSizeOf)] struct DecompressionContext { decoder: Decoder, is_ended: bool, } impl DecompressionContext { fn new(format: CompressionFormat) -> DecompressionContext { let decoder = match format { CompressionFormat::Brotli => { Decoder::Brotli(Box::new(BrotliDecoder::new(Vec::new(), BROTLI_BUFFER_SIZE))) }, CompressionFormat::Deflate => Decoder::Deflate(ZlibDecoder::new(Vec::new())), CompressionFormat::Deflate_raw => Decoder::DeflateRaw(DeflateDecoder::new(Vec::new())), CompressionFormat::Gzip => Decoder::Gzip(GzDecoder::new(Vec::new())), }; DecompressionContext { decoder, is_ended: false, } } fn decompress(&mut self, mut chunk: &[u8]) -> Result<Vec<u8>, io::Error> { let mut result = Vec::new(); match &mut self.decoder { Decoder::Brotli(decoder) => { while !chunk.is_empty() { let written = decoder.write(chunk)?; if written == 0 { self.is_ended = true; break; } chunk = &chunk[written..]; } decoder.flush()?; result.append(decoder.get_mut()); }, Decoder::Deflate(decoder) => { while !chunk.is_empty() { let written = decoder.write(chunk)?; if written == 0 { self.is_ended = true; break; } chunk = &chunk[written..]; } decoder.flush()?; result.append(decoder.get_mut()); }, Decoder::DeflateRaw(decoder) => { while !chunk.is_empty() { let written = decoder.write(chunk)?; if written == 0 { self.is_ended = true; break; } chunk = &chunk[written..]; } decoder.flush()?; result.append(decoder.get_mut()); }, Decoder::Gzip(decoder) => { while !chunk.is_empty() { let written = decoder.write(chunk)?; if written == 0 { self.is_ended = true; break; } chunk = &chunk[written..]; } decoder.flush()?; result.append(decoder.get_mut()); }, } Ok(result) } fn finalize(&mut self) -> Result<Vec<u8>, io::Error> { let mut result = Vec::new(); match &mut self.decoder { Decoder::Brotli(decoder) => { if decoder.close().is_ok() { self.is_ended = true; }; result.append(decoder.get_mut()); }, Decoder::Deflate(decoder) => { // Compressed data in "Deflate" format does not have trailing bytes. Therefore, // `ZlibEncoder::try_finish` is designed not to throw an error when the end of // compressed input has not been reached, in order to decompress as much of the // input as possible. // // To detect whether the end is reached, the workaround is to write one more byte to // the encoder. Refusing to take the extra byte indicates the end has been reached. // // Note that we need to pull out the data in buffer first to avoid the extra byte // contaminate the output. decoder.flush()?; result.append(decoder.get_mut()); if decoder.write(&[0])? == 0 { self.is_ended = true; } decoder.try_finish()?; }, Decoder::DeflateRaw(decoder) => { if decoder.try_finish().is_ok() { self.is_ended = true; }; result.append(decoder.get_mut()); }, Decoder::Gzip(decoder) => { if decoder.try_finish().is_ok() { self.is_ended = true; }; result.append(decoder.get_mut()); }, } Ok(result) } }