1#[cfg(feature = "brotli-compression-stream")]
6use std::borrow::BorrowMut;
7use std::cell::RefCell;
8use std::io::{self, Write};
9use std::ptr;
10
11#[cfg(feature = "brotli-compression-stream")]
12use brotli::CompressorWriter as BrotliEncoder;
13use dom_struct::dom_struct;
14use flate2::Compression;
15use flate2::write::{DeflateEncoder, GzEncoder, ZlibEncoder};
16use js::context::JSContext;
17use js::jsapi::JSObject;
18use js::jsval::UndefinedValue;
19use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue};
20use js::typedarray::Uint8;
21#[cfg(feature = "brotli-compression-stream")]
22use malloc_size_of::MallocShallowSizeOf;
23use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
24use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
25
26use crate::dom::bindings::buffer_source::{create_buffer_source, get_buffer_source_slice};
27use crate::dom::bindings::codegen::Bindings::CompressionStreamBinding::{
28 CompressionFormat, CompressionStreamMethods,
29};
30use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer;
31use crate::dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible};
32use crate::dom::bindings::error::{Error, Fallible};
33use crate::dom::bindings::root::{Dom, DomRoot};
34use crate::dom::stream::transformstreamdefaultcontroller::TransformerType;
35use crate::dom::types::{
36 GlobalScope, ReadableStream, TransformStream, TransformStreamDefaultController, WritableStream,
37};
38
39#[cfg(feature = "brotli-compression-stream")]
40pub(crate) const BROTLI_BUFFER_SIZE: usize = 4096;
41#[cfg(feature = "brotli-compression-stream")]
42const BROTLI_QUALITIY_LEVEL: u32 = 5;
43#[cfg(feature = "brotli-compression-stream")]
44const BROTLI_WINDOW_SIZE: u32 = 22;
45
46#[dom_struct]
48pub(crate) struct CompressionStream {
49 reflector_: Reflector,
50
51 transform: Dom<TransformStream>,
53
54 format: CompressionFormat,
56
57 #[no_trace]
59 context: RefCell<CompressionContext>,
60}
61
62impl CompressionStream {
63 fn new_inherited(
64 transform: &TransformStream,
65 format: CompressionFormat,
66 ) -> Fallible<CompressionStream> {
67 Ok(CompressionStream {
68 reflector_: Reflector::new(),
69 transform: Dom::from_ref(transform),
70 format,
71 context: RefCell::new(CompressionContext::new(format)?),
72 })
73 }
74 #[cfg_attr(crown, expect(crown::unrooted_must_root))] fn new_with_proto(
76 cx: &mut JSContext,
77 global: &GlobalScope,
78 proto: Option<SafeHandleObject>,
79 transform: &TransformStream,
80 format: CompressionFormat,
81 ) -> Fallible<DomRoot<CompressionStream>> {
82 Ok(reflect_dom_object_with_proto(
83 cx,
84 Box::new(CompressionStream::new_inherited(transform, format)?),
85 global,
86 proto,
87 ))
88 }
89}
90
91impl CompressionStreamMethods<crate::DomTypeHolder> for CompressionStream {
92 fn Constructor(
94 cx: &mut JSContext,
95 global: &GlobalScope,
96 proto: Option<SafeHandleObject>,
97 format: CompressionFormat,
98 ) -> Fallible<DomRoot<CompressionStream>> {
99 let transform = TransformStream::new_with_proto(cx, global, None);
105 let compression_stream =
106 CompressionStream::new_with_proto(cx, global, proto, &transform, format)?;
107
108 transform.set_up(
116 cx,
117 global,
118 TransformerType::Compressor(compression_stream.as_traced()),
119 )?;
120
121 Ok(compression_stream)
122 }
123
124 fn Readable(&self) -> DomRoot<ReadableStream> {
126 self.transform.get_readable()
128 }
129
130 fn Writable(&self) -> DomRoot<WritableStream> {
132 self.transform.get_writable()
134 }
135}
136
137pub(crate) fn compress_and_enqueue_a_chunk(
139 cx: &mut JSContext,
140 global: &GlobalScope,
141 cs: &CompressionStream,
142 chunk: SafeHandleValue,
143 controller: &TransformStreamDefaultController,
144) -> Fallible<()> {
145 let conversion_result =
147 ArrayBufferViewOrArrayBuffer::from_jsval(cx, chunk, ()).map_err(|_| {
148 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
149 })?;
150 let buffer_source = conversion_result.get_success_value().ok_or_else(|| {
151 Error::Type(c"Unable to convert chunk into ArrayBuffer or ArrayBufferView".to_owned())
152 })?;
153
154 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
157 let array = {
158 let mut compression_context = cs.context.borrow_mut();
159 let buffer = compression_context
160 .compress(get_buffer_source_slice(buffer_source, cx.no_gc()))
161 .map_err(|_| Error::Operation(Some("Failed to compress a chunk of input".into())))?;
162
163 if buffer.is_empty() {
165 return Ok(());
166 }
167 create_buffer_source::<Uint8>(cx, buffer, js_object.handle_mut())
171 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?
172 };
173 rooted!(&in(cx) let mut rval = UndefinedValue());
175 array.to_jsval(cx, rval.handle_mut());
176 controller.enqueue(cx, global, rval.handle())?;
177
178 Ok(())
179}
180
181pub(crate) fn compress_flush_and_enqueue(
183 cx: &mut JSContext,
184 global: &GlobalScope,
185 cs: &CompressionStream,
186 controller: &TransformStreamDefaultController,
187) -> Fallible<()> {
188 rooted!(&in(cx) let mut js_object = ptr::null_mut::<JSObject>());
192 let array = {
193 let mut compression_context = cs.context.borrow_mut();
194 let buffer = compression_context.finalize().map_err(|_| {
195 Error::Operation(Some("Failed to finalize the compression stream".into()))
196 })?;
197
198 if buffer.is_empty() {
200 return Ok(());
201 }
202 create_buffer_source::<Uint8>(cx, buffer, js_object.handle_mut())
206 .map_err(|_| Error::Type(c"Cannot convert byte sequence to Uint8Array".to_owned()))?
207 };
208 rooted!(&in(cx) let mut rval = UndefinedValue());
210 array.to_jsval(cx, rval.handle_mut());
211 controller.enqueue(cx, global, rval.handle())?;
212
213 Ok(())
214}
215
216enum Encoder {
218 #[cfg(feature = "brotli-compression-stream")]
219 Brotli(Box<BrotliEncoder<Vec<u8>>>),
220 Deflate(ZlibEncoder<Vec<u8>>),
221 DeflateRaw(DeflateEncoder<Vec<u8>>),
222 Gzip(GzEncoder<Vec<u8>>),
223}
224
225impl MallocSizeOf for Encoder {
226 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
227 match self {
228 #[cfg(feature = "brotli-compression-stream")]
229 Encoder::Brotli(encoder) => encoder.shallow_size_of(ops),
230 Encoder::Deflate(encoder) => encoder.size_of(ops),
231 Encoder::DeflateRaw(encoder) => encoder.size_of(ops),
232 Encoder::Gzip(encoder) => encoder.size_of(ops),
233 }
234 }
235}
236
237#[derive(MallocSizeOf)]
240struct CompressionContext {
241 encoder: Encoder,
242}
243
244impl CompressionContext {
245 fn new(format: CompressionFormat) -> Fallible<CompressionContext> {
246 let encoder = match format {
247 #[cfg(feature = "brotli-compression-stream")]
248 CompressionFormat::Brotli => Encoder::Brotli(Box::new(BrotliEncoder::new(
249 Vec::new(),
250 BROTLI_BUFFER_SIZE,
251 BROTLI_QUALITIY_LEVEL,
252 BROTLI_WINDOW_SIZE,
253 ))),
254 #[cfg(not(feature = "brotli-compression-stream"))]
255 CompressionFormat::Brotli => {
256 return Err(Error::NotSupported(Some("Brotli not supported".into())));
257 },
258 CompressionFormat::Deflate => {
259 Encoder::Deflate(ZlibEncoder::new(Vec::new(), Compression::default()))
260 },
261 CompressionFormat::Deflate_raw => {
262 Encoder::DeflateRaw(DeflateEncoder::new(Vec::new(), Compression::default()))
263 },
264 CompressionFormat::Gzip => {
265 Encoder::Gzip(GzEncoder::new(Vec::new(), Compression::default()))
266 },
267 };
268 Ok(CompressionContext { encoder })
269 }
270
271 fn compress(&mut self, chunk: &[u8]) -> Result<&[u8], io::Error> {
272 match &mut self.encoder {
273 #[cfg(feature = "brotli-compression-stream")]
274 Encoder::Brotli(encoder) => {
275 encoder.get_mut().clear();
276 encoder.write_all(chunk)?;
277 encoder.flush()?;
278 Ok(encoder.get_ref().as_slice())
279 },
280 Encoder::Deflate(encoder) => {
281 encoder.get_mut().clear();
282 encoder.write_all(chunk)?;
283 encoder.flush()?;
284 Ok(encoder.get_ref().as_slice())
285 },
286 Encoder::DeflateRaw(encoder) => {
287 encoder.get_mut().clear();
288 encoder.write_all(chunk)?;
289 encoder.flush()?;
290 Ok(encoder.get_ref().as_slice())
291 },
292 Encoder::Gzip(encoder) => {
293 encoder.get_mut().clear();
294 encoder.write_all(chunk)?;
295 encoder.flush()?;
296 Ok(encoder.get_ref().as_slice())
297 },
298 }
299 }
300
301 fn finalize(&mut self) -> Result<&[u8], io::Error> {
302 match &mut self.encoder {
303 #[cfg(feature = "brotli-compression-stream")]
304 Encoder::Brotli(encoder) => {
305 encoder.get_mut().clear();
306 let finished_encoder = std::mem::replace(
307 encoder.borrow_mut(),
308 BrotliEncoder::new(
309 Vec::new(),
310 BROTLI_BUFFER_SIZE,
311 BROTLI_QUALITIY_LEVEL,
312 BROTLI_WINDOW_SIZE,
313 ),
314 );
315 *encoder.get_mut() = finished_encoder.into_inner();
316 Ok(encoder.get_ref().as_slice())
317 },
318 Encoder::Deflate(encoder) => {
319 encoder.get_mut().clear();
320 encoder.try_finish()?;
321 Ok(encoder.get_ref().as_slice())
322 },
323 Encoder::DeflateRaw(encoder) => {
324 encoder.get_mut().clear();
325 encoder.try_finish()?;
326 Ok(encoder.get_ref().as_slice())
327 },
328 Encoder::Gzip(encoder) => {
329 encoder.get_mut().clear();
330 encoder.try_finish()?;
331 Ok(encoder.get_ref().as_slice())
332 },
333 }
334 }
335}