1use std::cell::RefCell;
6use std::io::{self, Write};
7use std::ptr;
8
9use brotli::CompressorWriter as BrotliEncoder;
10use dom_struct::dom_struct;
11use flate2::Compression;
12use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
13use js::jsapi::JSObject;
14use js::jsval::UndefinedValue;
15use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
16use js::typedarray::Uint8;
17use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
18
19use crate::dom::bindings::buffer_source::create_buffer_source;
20use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::{
21 CompressionFormat, CompressionStreamMethods,
22};
23use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
24use crate::dom::bindings::conversions::{SafeFromJSValConvertible, SafeToJSValConvertible};
25use crate::dom::bindings::error::{Error, Fallible};
26use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
27use crate::dom::bindings::root::{Dom, DomRoot};
28use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
29use crate::dom::types::{
30 GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
31};
32use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
33
34enum Compressor {
37 Deflate(ZlibEncoder<Vec<u8>>),
38 DeflateRaw(DeflateEncoder<Vec<u8>>),
39 Gzip(GzEncoder<Vec<u8>>),
40 Brotli(Box<BrotliEncoder<Vec<u8>>>),
41}
42
43impl Compressor {
45 fn new(format: CompressionFormat) -> Compressor {
46 match format {
47 CompressionFormat::Deflate => {
48 Compressor::Deflate(ZlibEncoder::new(Vec::new(), Compression::default()))
49 },
50 CompressionFormat::Deflate_raw => {
51 Compressor::DeflateRaw(DeflateEncoder::new(Vec::new(), Compression::default()))
52 },
53 CompressionFormat::Gzip => {
54 Compressor::Gzip(GzEncoder::new(Vec::new(), Compression::default()))
55 },
56 CompressionFormat::Brotli => {
57 Compressor::Brotli(Box::new(BrotliEncoder::new(Vec::new(), 4096, 5, 22)))
58 },
59 }
60 }
61
62 fn get_ref(&self) -> &Vec<u8> {
63 match self {
64 Compressor::Deflate(zlib_encoder) => zlib_encoder.get_ref(),
65 Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.get_ref(),
66 Compressor::Gzip(gz_encoder) => gz_encoder.get_ref(),
67 Compressor::Brotli(brotli_encoder) => brotli_encoder.get_ref(),
68 }
69 }
70
71 fn get_mut(&mut self) -> &mut Vec<u8> {
72 match self {
73 Compressor::Deflate(zlib_encoder) => zlib_encoder.get_mut(),
74 Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.get_mut(),
75 Compressor::Gzip(gz_encoder) => gz_encoder.get_mut(),
76 Compressor::Brotli(brotli_encoder) => brotli_encoder.get_mut(),
77 }
78 }
79
80 fn write_all(&mut self, buf: &[u8]) -> Result<(), io::Error> {
81 match self {
82 Compressor::Deflate(zlib_encoder) => zlib_encoder.write_all(buf),
83 Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.write_all(buf),
84 Compressor::Gzip(gz_encoder) => gz_encoder.write_all(buf),
85 Compressor::Brotli(brotli_encoder) => brotli_encoder.write_all(buf),
86 }
87 }
88
89 fn flush(&mut self) -> io::Result<()> {
90 match self {
91 Compressor::Deflate(zlib_encoder) => zlib_encoder.flush(),
92 Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.flush(),
93 Compressor::Gzip(gz_encoder) => gz_encoder.flush(),
94 Compressor::Brotli(brotli_encoder) => brotli_encoder.flush(),
95 }
96 }
97
98 fn try_finish(&mut self) -> io::Result<()> {
99 match self {
100 Compressor::Deflate(zlib_encoder) => zlib_encoder.try_finish(),
101 Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.try_finish(),
102 Compressor::Gzip(gz_encoder) => gz_encoder.try_finish(),
103 Compressor::Brotli(brotli_encoder) => brotli_encoder.flush(),
104 }
105 }
106}
107
108impl MallocSizeOf for Compressor {
109 #[expect(unsafe_code)]
110 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
111 match self {
112 Compressor::Deflate(zlib_encoder) => zlib_encoder.size_of(ops),
113 Compressor::DeflateRaw(deflate_encoder) => deflate_encoder.size_of(ops),
114 Compressor::Gzip(gz_encoder) => gz_encoder.size_of(ops),
115 Compressor::Brotli(brotli_dencoder) => unsafe {
116 ops.malloc_size_of(&**brotli_dencoder)
117 },
118 }
119 }
120}
121
122#[dom_struct]
124pub(crate) struct CompressionStream {
125 reflector_: Reflector,
126
127 transform: Dom<TransformStream>,
129
130 format: CompressionFormat,
132
133 #[no_trace]
135 context: RefCell<Compressor>,
136}
137
138impl CompressionStream {
139 fn new_inherited(transform: &TransformStream, format: CompressionFormat) -> CompressionStream {
140 CompressionStream {
141 reflector_: Reflector::new(),
142 transform: Dom::from_ref(transform),
143 format,
144 context: RefCell::new(Compressor::new(format)),
145 }
146 }
147
148 fn new_with_proto(
149 global: &GlobalScope,
150 proto: Option<SafeHandleObject>,
151 transform: &TransformStream,
152 format: CompressionFormat,
153 can_gc: CanGc,
154 ) -> DomRoot<CompressionStream> {
155 reflect_dom_object_with_proto(
156 Box::new(CompressionStream::new_inherited(transform, format)),
157 global,
158 proto,
159 can_gc,
160 )
161 }
162}
163
164impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
165 fn Constructor(
167 global: &GlobalScope,
168 proto: Option<SafeHandleObject>,
169 can_gc: CanGc,
170 format: CompressionFormat,
171 ) -> Fallible<DomRoot<CompressionStream>> {
172 let transform = TransformStream::new_with_proto(global, None, can_gc);
178 let compression_stream =
179 CompressionStream::new_with_proto(global, proto, &transform, format, can_gc);
180
181 let transformer_type = TransformerType::Compressor(compression_stream.clone());
186
187 let cx = GlobalScope::get_cx();
190 transform.set_up(cx, global, transformer_type, can_gc)?;
191
192 Ok(compression_stream)
193 }
194
195 fn Readable(&self) -> DomRoot<ReadableStream> {
197 self.transform.get_readable()
199 }
200
201 fn Writable(&self) -> DomRoot<WritableStream> {
203 self.transform.get_writable()
205 }
206}
207
208pub(crate) fn compress_and_enqueue_a_chunk(
210 cx: &mut js::context::JSContext,
211 global: &GlobalScope,
212 cs: &CompressionStream,
213 chunk: SafeHandleValue,
214 controller: &TransformStreamDefaultController,
215) -> Fallible<()> {
216 let chunk = convert_chunk_to_vec(cx.into(), chunk, CanGc::from_cx(cx))?;
218
219 let mut compressor = cs.context.borrow_mut();
222 let offset = compressor.get_ref().len();
223 compressor
224 .write_all(&chunk)
225 .map_err(|_| Error::Type(c"CompressionStream: write_all() failed".to_owned()))?;
226 compressor
227 .flush()
228 .map_err(|_| Error::Type(c"CompressionStream: flush() failed".to_owned()))?;
229 let buffer = &compressor.get_ref()[offset..];
230
231 if buffer.is_empty() {
233 return Ok(());
234 }
235
236 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
241 let buffer_source = create_buffer_source::<Uint8>(
242 cx.into(),
243 buffer,
244 js_object.handle_mut(),
245 CanGc::from_cx(cx),
246 )
247 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
248 rooted!(&in(cx) let mut rval = UndefinedValue());
249 buffer_source.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
250 controller.enqueue(cx, global, rval.handle())?;
251
252 compressor.get_mut().clear();
255
256 Ok(())
257}
258
259pub(crate) fn compress_flush_and_enqueue(
261 cx: &mut js::context::JSContext,
262 global: &GlobalScope,
263 cs: &CompressionStream,
264 controller: &TransformStreamDefaultController,
265) -> Fallible<()> {
266 let mut compressor = cs.context.borrow_mut();
270 let offset = compressor.get_ref().len();
271 compressor
272 .try_finish()
273 .map_err(|_| Error::Type(c"CompressionStream: try_finish() failed".to_owned()))?;
274 let buffer = &compressor.get_ref()[offset..];
275
276 if buffer.is_empty() {
278 return Ok(());
279 }
280
281 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
286 let buffer_source = create_buffer_source::<Uint8>(
287 cx.into(),
288 buffer,
289 js_object.handle_mut(),
290 CanGc::from_cx(cx),
291 )
292 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?;
293 rooted!(&in(cx) let mut rval = UndefinedValue());
294 buffer_source.safe_to_jsval(cx.into(), rval.handle_mut(), CanGc::from_cx(cx));
295 controller.enqueue(cx, global, rval.handle())?;
296
297 compressor.get_mut().clear();
300
301 Ok(())
302}
303
304pub(crate) fn convert_chunk_to_vec(
305 cx: SafeJSContext,
306 chunk: SafeHandleValue,
307 can_gc: CanGc,
308) -> Result<Vec<u8>, Error> {
309 let conversion_result = ArrayBufferViewOrArrayBuffer::safe_from_jsval(cx, chunk, (), can_gc)
310 .map_err(|_| {
311 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
312 })?;
313 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
314 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
315 })?;
316 match buffer_source {
317 ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => Ok(view.to_vec()),
318 ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => Ok(buffer.to_vec()),
319 }
320}