diff --git a/Cargo.lock b/Cargo.lock index 7f58e144b5..930a054ee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,9 +316,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "brotli" -version = "3.3.2" +version = "3.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71cb90ade945043d3d53597b2fc359bb063db8ade2bcffe7997351d0756e9d50" +checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -870,9 +870,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ff82ae22a4012a843799f5baadda95ddc67cc3bed21fe5ebcda2c5cff768a1" +checksum = "e82f45d6513b789c04adf0915ca3c3c696f5f5f9379a82834ad4f783fbaa8b3a" dependencies = [ "cfg-if 1.0.0", "deno_ast", @@ -1074,6 +1074,7 @@ dependencies = [ "base64 0.13.0", "deno_core", "encoding_rs", + "flate2", "serde", "tokio", "uuid", @@ -2150,9 +2151,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi 0.3.9", @@ -3448,9 +3449,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" +checksum = "c059c05b48c5c0067d4b4b2b4f0732dd65feb52daf7e0ea09cd87e7dadc1af79" dependencies = [ "indexmap", "itoa 1.0.1", @@ -3545,9 +3546,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" +checksum = "ba1eead9e94aa5a2e02de9e7839f96a007f686ae7a1d57c7797774810d24908a" [[package]] name = "slab" @@ -3778,9 +3779,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.65.1" +version = "0.65.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279a4595711a9aaf02165398d5f317b89d718ab90d30b2c08f34abda6545aa3a" +checksum = "3a82d26122365a721a7df46bd3e4240fbf19188d9ccf18c7faaa4d12dfc51488" dependencies = [ "is-macro", "num-bigint", @@ -3850,9 +3851,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.87.0" +version = "0.87.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c454cbd95ab84fd9ffc5059cffad80b7711bd4b8732257a7c83f4b2c809dfa1" +checksum = "f032c57793a287a8b374e92a2b606e5eb890539285e4723fe6acb9be1fadcec6" dependencies = [ "either", "enum_kind", @@ -3870,9 +3871,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.111.1" +version = "0.111.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae472031803e9fddb9f9141edc3ec0c3bff5904cd08e0509f2ad88689678ce7c" +checksum = "8dd7878bd017ed942fdcd5a892fa4990c21b1fe49c4aca7be1ecba16403e7052" dependencies = [ "swc_atoms", "swc_common", @@ -3889,9 +3890,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.56.0" +version = "0.56.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74824b2df0931ed3d84201e50138b68d1dc78a802aef9dbdec4ea0ffcea8a28b" +checksum = "d62c460e81027cdda2325348c1e5c0233c59127ab3227a45aaeac80eac7c5df1" dependencies = [ "once_cell", "phf", @@ -3909,9 +3910,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.43.0" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a972a157b9104b7979a34556841f93e98d18d70b83c9e625b35dd390b57e59f" +checksum = "5cd6d1fbe53dfa365827eddadbafec687c7afecd294c54a62fedcd92c4e44293" dependencies = [ "swc_atoms", "swc_common", @@ -3936,9 +3937,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.81.0" +version = "0.81.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af299ad73dc689a0516950613a9262203feff76adc3f2f14307a12bdd5835c9" +checksum = "653395bad3ace0b3dcc99d9edf45bd08bbd97f48f8aec519d5b8ac336779ee00" dependencies = [ "ahash", "dashmap", @@ -3958,9 +3959,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.73.0" +version = "0.73.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe1cf222cfe757700ffea622589990b1b0ef0a18c85ffe6e8442ba515257aa98" +checksum = "da8cb3be65a35abfef0f4311d3f76dda381162a4920e526e3e7ac39d693df360" dependencies = [ "either", "serde", @@ -3978,9 +3979,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.75.0" +version = "0.75.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee01d3f5c0f424a199a4d13fc6fbe14cfd88ccff0b778558311075477e25310" +checksum = "4d9e590632bfd9b958f7b68f408040a320a95920021a241f1a403e6670f004cb" dependencies = [ "ahash", "base64 0.13.0", @@ -4003,9 +4004,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.77.1" +version = "0.77.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50560b92ecbd43ee4dea19ae763d0bde8d753c8401d76115c714cfb51f838e76" +checksum = "29fe8ca3b3ad557407449fcc653acde3026aa6078fa4307ae6b318a35514fd8f" dependencies = [ "serde", "swc_atoms", @@ -4048,9 +4049,9 @@ dependencies = [ [[package]] name = "swc_ecmascript" -version = "0.108.2" +version = "0.108.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae93ca691a78c072b76045f70ffb9daf73e6e21530862c627c35a9a20bbd6d69" +checksum = "5feaed38d2e24849c1b1bb725ea455855ebe4bff52332a491cabc07d107db322" dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", @@ -5109,9 +5110,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.2.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73" +checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb" dependencies = [ "proc-macro2 1.0.36", "quote 1.0.14", diff --git a/ext/web/14_compression.js b/ext/web/14_compression.js new file mode 100644 index 0000000000..1a0f77e665 --- /dev/null +++ b/ext/web/14_compression.js @@ -0,0 +1,123 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +// @ts-check +/// +/// +/// + +"use strict"; + +((window) => { + const core = window.Deno.core; + const webidl = window.__bootstrap.webidl; + const { TransformStream } = window.__bootstrap.streams; + + webidl.converters.CompressionFormat = webidl.createEnumConverter( + "CompressionFormat", + [ + "deflate", + "gzip", + ], + ); + + class CompressionStream { + #transform; + + constructor(format) { + const prefix = "Failed to construct 'CompressionStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + format = webidl.converters.CompressionFormat(format, { + prefix, + context: "Argument 1", + }); + + const rid = core.opSync("op_compression_new", format, false); + + this.#transform = new TransformStream({ + transform(chunk, controller) { + // TODO(lucacasonato): convert chunk to BufferSource + const output = core.opSync( + "op_compression_write", + rid, + chunk, + ); + maybeEnqueue(controller, output); + }, + flush(controller) { + const output = core.opSync("op_compression_finish", rid); + maybeEnqueue(controller, output); + }, + }); + + this[webidl.brand] = webidl.brand; + } + + get readable() { + webidl.assertBranded(this, CompressionStream); + return this.#transform.readable; + } + + get writable() { + webidl.assertBranded(this, CompressionStream); + return this.#transform.writable; + } + } + + webidl.configurePrototype(CompressionStream); + + class DecompressionStream { + #transform; + + constructor(format) { + const prefix = "Failed to construct 'DecompressionStream'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + format = webidl.converters.CompressionFormat(format, { + prefix, + context: "Argument 1", + }); + + const rid = core.opSync("op_compression_new", format, true); + + this.#transform = new TransformStream({ + transform(chunk, controller) { + // TODO(lucacasonato): convert chunk to BufferSource + const output = core.opSync( + "op_compression_write", + rid, + chunk, + ); + maybeEnqueue(controller, output); + }, + flush(controller) { + const output = core.opSync("op_compression_finish", rid); + maybeEnqueue(controller, output); + }, + }); + + this[webidl.brand] = webidl.brand; + } + + get readable() { + webidl.assertBranded(this, DecompressionStream); + return this.#transform.readable; + } + + get writable() { + webidl.assertBranded(this, DecompressionStream); + return this.#transform.writable; + } + } + + function maybeEnqueue(controller, output) { + if (output && output.byteLength > 0) { + controller.enqueue(output); + } + } + + webidl.configurePrototype(DecompressionStream); + + window.__bootstrap.compression = { + CompressionStream, + DecompressionStream, + }; +})(globalThis); diff --git a/ext/web/Cargo.toml b/ext/web/Cargo.toml index 31982d5901..14523e206f 100644 --- a/ext/web/Cargo.toml +++ b/ext/web/Cargo.toml @@ -18,6 +18,7 @@ async-trait = "0.1.51" base64 = "0.13.0" deno_core = { version = "0.116.0", path = "../../core" } encoding_rs = "0.8.29" +flate2 = "1" serde = "1.0.129" tokio = { version = "1.10.1", features = ["full"] } uuid = { version = "0.8.2", features = ["v4", "serde"] } diff --git a/ext/web/compression.rs b/ext/web/compression.rs new file mode 100644 index 0000000000..c84db75506 --- /dev/null +++ b/ext/web/compression.rs @@ -0,0 +1,104 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ResourceId; +use deno_core::ZeroCopyBuf; +use flate2::write::GzDecoder; +use flate2::write::GzEncoder; +use flate2::write::ZlibDecoder; +use flate2::write::ZlibEncoder; +use flate2::Compression; +use std::borrow::Cow; +use std::cell::RefCell; +use std::io::Write; +use std::rc::Rc; + +#[derive(Debug)] +struct CompressionResource(RefCell); + +#[derive(Debug)] +enum Inner { + DeflateDecoder(ZlibDecoder>), + DeflateEncoder(ZlibEncoder>), + GzDecoder(GzDecoder>), + GzEncoder(GzEncoder>), +} + +impl Resource for CompressionResource { + fn name(&self) -> Cow { + "compression".into() + } +} + +pub fn op_compression_new( + state: &mut OpState, + format: String, + is_decoder: bool, +) -> Result { + let w = Vec::new(); + let inner = match (format.as_str(), is_decoder) { + ("deflate", true) => Inner::DeflateDecoder(ZlibDecoder::new(w)), + ("deflate", false) => { + Inner::DeflateEncoder(ZlibEncoder::new(w, Compression::default())) + } + ("gzip", true) => Inner::GzDecoder(GzDecoder::new(w)), + ("gzip", false) => { + Inner::GzEncoder(GzEncoder::new(w, Compression::default())) + } + _ => unreachable!(), + }; + let resource = CompressionResource(RefCell::new(inner)); + Ok(state.resource_table.add(resource)) +} + +pub fn op_compression_write( + state: &mut OpState, + rid: ResourceId, + input: ZeroCopyBuf, +) -> Result { + let resource = state.resource_table.get::(rid)?; + let mut inner = resource.0.borrow_mut(); + let out: Vec = match &mut *inner { + Inner::DeflateDecoder(d) => { + d.write_all(&input)?; + d.flush()?; + d.get_mut().drain(..) + } + Inner::DeflateEncoder(d) => { + d.write_all(&input)?; + d.flush()?; + d.get_mut().drain(..) + } + Inner::GzDecoder(d) => { + d.write_all(&input)?; + d.flush()?; + d.get_mut().drain(..) + } + Inner::GzEncoder(d) => { + d.write_all(&input)?; + d.flush()?; + d.get_mut().drain(..) + } + } + .collect(); + Ok(out.into()) +} + +pub fn op_compression_finish( + state: &mut OpState, + rid: ResourceId, + _: (), +) -> Result { + let resource = state.resource_table.take::(rid)?; + let resource = Rc::try_unwrap(resource).unwrap(); + let inner = resource.0.into_inner(); + let out: Vec = match inner { + Inner::DeflateDecoder(d) => d.finish()?, + Inner::DeflateEncoder(d) => d.finish()?, + Inner::GzDecoder(d) => d.finish()?, + Inner::GzEncoder(d) => d.finish()?, + }; + Ok(out.into()) +} diff --git a/ext/web/lib.deno_web.d.ts b/ext/web/lib.deno_web.d.ts index ce20f08b10..1233b842f7 100644 --- a/ext/web/lib.deno_web.d.ts +++ b/ext/web/lib.deno_web.d.ts @@ -809,3 +809,17 @@ declare function structuredClone( value: any, options?: StructuredSerializeOptions, ): any; + +declare class CompressionStream { + constructor(format: string); + + readonly readable: ReadableStream; + readonly writable: WritableStream; +} + +declare class DecompressionStream { + constructor(format: string); + + readonly readable: ReadableStream; + readonly writable: WritableStream; +} diff --git a/ext/web/lib.rs b/ext/web/lib.rs index 281e55e06e..b32deeb972 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -1,6 +1,7 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. mod blob; +mod compression; mod message_port; use deno_core::error::range_error; @@ -66,6 +67,7 @@ pub fn init(blob_store: BlobStore, maybe_location: Option) -> Extension { "11_blob_url.js", "12_location.js", "13_message_port.js", + "14_compression.js", )) .ops(vec![ ("op_base64_decode", op_sync(op_base64_decode)), @@ -102,6 +104,18 @@ pub fn init(blob_store: BlobStore, maybe_location: Option) -> Extension { "op_message_port_recv_message", op_async(op_message_port_recv_message), ), + ( + "op_compression_new", + op_sync(compression::op_compression_new), + ), + ( + "op_compression_write", + op_sync(compression::op_compression_write), + ), + ( + "op_compression_finish", + op_sync(compression::op_compression_finish), + ), ]) .state(move |state| { state.put(blob_store.clone()); diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 0c5555dd0f..ba2b367053 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -37,6 +37,7 @@ delete Object.prototype.__proto__; const encoding = window.__bootstrap.encoding; const colors = window.__bootstrap.colors; const Console = window.__bootstrap.console.Console; + const compression = window.__bootstrap.compression; const worker = window.__bootstrap.worker; const internals = window.__bootstrap.internals; const performance = window.__bootstrap.performance; @@ -362,11 +363,13 @@ delete Object.prototype.__proto__; streams.ByteLengthQueuingStrategy, ), CloseEvent: util.nonEnumerable(CloseEvent), + CompressionStream: util.nonEnumerable(compression.CompressionStream), CountQueuingStrategy: util.nonEnumerable( streams.CountQueuingStrategy, ), CryptoKey: util.nonEnumerable(crypto.CryptoKey), CustomEvent: util.nonEnumerable(CustomEvent), + DecompressionStream: util.nonEnumerable(compression.DecompressionStream), DOMException: util.nonEnumerable(domException.DOMException), ErrorEvent: util.nonEnumerable(ErrorEvent), Event: util.nonEnumerable(Event), diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 6c8b2e4d07..f5cb0171d1 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -4030,5 +4030,37 @@ "Pattern: [{\"pathname\":\"*/{*}\"}] Inputs: [{\"pathname\":\"foo/bar\"}]", "Pattern: [{\"pathname\":\"*//*\"}] Inputs: [{\"pathname\":\"foo/bar\"}]" ] + }, + "compression": { + "compression-bad-chunks.tentative.any.html": true, + "compression-bad-chunks.tentative.any.worker.html": true, + "compression-including-empty-chunk.tentative.any.html": true, + "compression-including-empty-chunk.tentative.any.worker.html": true, + "compression-multiple-chunks.tentative.any.html": true, + "compression-multiple-chunks.tentative.any.worker.html": true, + "compression-output-length.tentative.any.html": true, + "compression-output-length.tentative.any.worker.html": true, + "compression-stream.tentative.any.html": true, + "compression-stream.tentative.any.worker.html": true, + "compression-with-detach.tentative.window.html": true, + "decompression-bad-chunks.tentative.any.html": true, + "decompression-bad-chunks.tentative.any.worker.html": true, + "decompression-buffersource.tentative.any.html": true, + "decompression-buffersource.tentative.any.worker.html": true, + "decompression-constructor-error.tentative.any.html": true, + "decompression-constructor-error.tentative.any.worker.html": true, + "decompression-correct-input.tentative.any.html": true, + "decompression-correct-input.tentative.any.worker.html": true, + "decompression-corrupt-input.tentative.any.html": true, + "decompression-corrupt-input.tentative.any.worker.html": true, + "decompression-empty-input.tentative.any.html": true, + "decompression-empty-input.tentative.any.worker.html": true, + "decompression-split-chunk.tentative.any.html": true, + "decompression-split-chunk.tentative.any.worker.html": true, + "decompression-uint8array-output.tentative.any.html": true, + "decompression-uint8array-output.tentative.any.worker.html": true, + "decompression-with-detach.tentative.window.html": true, + "idlharness.https.any.html": true, + "idlharness.https.any.worker.html": true } } \ No newline at end of file