Commit 6e443093 authored by Arno Renevier's avatar Arno Renevier Committed by Commit Bot

CompressionStream

Implement compression for "gzip" and "deflate". That implementation is
hidden behing ExperimentalCompressionStream flag.

For unit tests, we compress and stream and uncompress it with pako, and
we make sure its output is the same as our original data.

Change-Id: I89dde0e06a07b695be93a3b4f81fa6b3ee80d760
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1763344
Commit-Queue: Adam Rice <ricea@chromium.org>
Reviewed-by: default avatarYutaka Hirano <yhirano@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarAdam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/master@{#704009}
parent ec713af5
...@@ -2,8 +2,12 @@ import("//third_party/blink/renderer/modules/modules.gni") ...@@ -2,8 +2,12 @@ import("//third_party/blink/renderer/modules/modules.gni")
blink_modules_sources("compression") { blink_modules_sources("compression") {
sources = [ sources = [
"compression_stream.cc",
"compression_stream.h",
"decompression_stream.cc", "decompression_stream.cc",
"decompression_stream.h", "decompression_stream.h",
"deflate_transformer.cc",
"deflate_transformer.h",
"inflate_transformer.cc", "inflate_transformer.cc",
"inflate_transformer.h", "inflate_transformer.h",
] ]
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/compression/compression_stream.h"
#include "third_party/blink/renderer/modules/compression/deflate_transformer.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
namespace blink {
CompressionStream* CompressionStream::Create(ScriptState* script_state,
const AtomicString& format,
ExceptionState& exception_state) {
return MakeGarbageCollected<CompressionStream>(script_state, format,
exception_state);
}
CompressionStream::~CompressionStream() = default;
ReadableStream* CompressionStream::readable() const {
return transform_->Readable();
}
WritableStream* CompressionStream::writable() const {
return transform_->Writable();
}
void CompressionStream::Trace(Visitor* visitor) {
visitor->Trace(transform_);
ScriptWrappable::Trace(visitor);
}
CompressionStream::CompressionStream(ScriptState* script_state,
const AtomicString& format,
ExceptionState& exception_state)
: transform_(MakeGarbageCollected<TransformStream>()) {
DeflateTransformer::Format deflate_format;
if (format == "gzip") {
deflate_format = DeflateTransformer::Format::Gzip;
} else if (format == "deflate") {
deflate_format = DeflateTransformer::Format::Deflate;
} else {
exception_state.ThrowTypeError("Unsupported format");
return;
}
// default level is hardcoded for now.
// TODO(arenevier): Make level configurable
const int deflate_level = 6;
transform_->Init(MakeGarbageCollected<DeflateTransformer>(
script_state, deflate_format, deflate_level),
script_state, exception_state);
}
} // namespace blink
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_COMPRESSION_COMPRESSION_STREAM_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_COMPRESSION_COMPRESSION_STREAM_H_
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/core/streams/transform_stream.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
namespace blink {
class CompressionStream final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO();
public:
static CompressionStream* Create(ScriptState*,
const AtomicString&,
ExceptionState&);
CompressionStream(ScriptState*, const AtomicString&, ExceptionState&);
~CompressionStream() override;
ReadableStream* readable() const;
WritableStream* writable() const;
void Trace(Visitor*) override;
private:
const Member<TransformStream> transform_;
DISALLOW_COPY_AND_ASSIGN(CompressionStream);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_COMPRESSION_COMPRESSION_STREAM_H_
[
Exposed=(Window,Worker),
Constructor(DOMString format),
ConstructorCallWith=ScriptState,
RuntimeEnabled=CompressionStreams,
RaisesException=Constructor
] interface CompressionStream {
readonly attribute ReadableStream readable;
readonly attribute WritableStream writable;
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/compression/deflate_transformer.h"
#include <string.h>
#include <algorithm>
#include <limits>
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h"
#include "third_party/blink/renderer/core/streams/transform_stream_default_controller_interface.h"
#include "third_party/blink/renderer/core/streams/transform_stream_transformer.h"
#include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/to_v8.h"
#include "v8/include/v8.h"
namespace blink {
DeflateTransformer::DeflateTransformer(ScriptState* script_state,
Format format,
int level)
: script_state_(script_state),
// TODO(arenevier): same as PODArena default chunk size. Is that
// reasonable? Or should we use a variable size buffer?
buffer_(16384) {
DCHECK(level >= 1 && level <= 9);
int window_bits = 15;
if (format == Format::Gzip) {
window_bits += 16;
}
memset(&stream_, 0, sizeof(z_stream));
int err = deflateInit2(&stream_, level, Z_DEFLATED, window_bits, 8,
Z_DEFAULT_STRATEGY);
DCHECK_EQ(Z_OK, err);
}
DeflateTransformer::~DeflateTransformer() {
if (!is_stream_freed_) {
deflateEnd(&stream_);
}
}
void DeflateTransformer::Transform(
v8::Local<v8::Value> chunk,
TransformStreamDefaultControllerInterface* controller,
ExceptionState& exception_state) {
NotShared<DOMUint8Array> chunk_data = ToNotShared<NotShared<DOMUint8Array>>(
script_state_->GetIsolate(), chunk, exception_state);
if (exception_state.HadException()) {
return;
}
if (!chunk_data) {
exception_state.ThrowTypeError("chunk is not of type Uint8Array.");
return;
}
if (chunk_data.View()->length() == 0) {
return;
}
Deflate(chunk_data.View(), false, controller, exception_state);
}
void DeflateTransformer::Flush(
TransformStreamDefaultControllerInterface* controller,
ExceptionState& exception_state) {
Deflate(nullptr, true, controller, exception_state);
is_stream_freed_ = true;
deflateEnd(&stream_);
}
void DeflateTransformer::Deflate(
const DOMUint8Array* data,
bool finished,
TransformStreamDefaultControllerInterface* controller,
ExceptionState& exception_state) {
if (data) {
stream_.avail_in = data->length();
stream_.next_in = data->DataMaybeShared();
} else {
stream_.avail_in = 0;
stream_.next_in = nullptr;
}
do {
stream_.avail_out = buffer_.size();
stream_.next_out = buffer_.data();
int err = deflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);
DCHECK((finished && err == Z_STREAM_END) || (err == Z_OK));
wtf_size_t bytes = buffer_.size() - stream_.avail_out;
if (bytes) {
controller->Enqueue(
ToV8(DOMUint8Array::Create(buffer_.data(), bytes), script_state_),
exception_state);
if (exception_state.HadException()) {
return;
}
}
} while (stream_.avail_out == 0);
}
void DeflateTransformer::Trace(Visitor* visitor) {
visitor->Trace(script_state_);
TransformStreamTransformer::Trace(visitor);
}
} // namespace blink
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_COMPRESSION_DEFLATE_TRANSFORMER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_COMPRESSION_DEFLATE_TRANSFORMER_H_
#include "third_party/blink/renderer/core/streams/transform_stream_transformer.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "third_party/zlib/zlib.h"
namespace blink {
class DeflateTransformer final : public TransformStreamTransformer {
public:
enum class Format { Gzip, Deflate };
DeflateTransformer(ScriptState*, Format, int level);
~DeflateTransformer() override;
void Transform(v8::Local<v8::Value> chunk,
TransformStreamDefaultControllerInterface*,
ExceptionState&) override;
void Flush(TransformStreamDefaultControllerInterface*,
ExceptionState&) override;
ScriptState* GetScriptState() override { return script_state_; }
void Trace(Visitor*) override;
private:
void Deflate(const DOMUint8Array* data,
bool finished,
TransformStreamDefaultControllerInterface*,
ExceptionState&);
Member<ScriptState> script_state_;
Vector<uint8_t> buffer_;
z_stream stream_;
bool is_stream_freed_ = false;
DISALLOW_COPY_AND_ASSIGN(DeflateTransformer);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_COMPRESSION_DEFLATE_TRANSFORMER_H_
...@@ -99,6 +99,7 @@ modules_idl_files = ...@@ -99,6 +99,7 @@ modules_idl_files =
"canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.idl", "canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.idl",
"clipboard/clipboard.idl", "clipboard/clipboard.idl",
"clipboard/clipboard_item.idl", "clipboard/clipboard_item.idl",
"compression/compression_stream.idl",
"compression/decompression_stream.idl", "compression/decompression_stream.idl",
"contacts_picker/contacts_manager.idl", "contacts_picker/contacts_manager.idl",
"content_index/content_index.idl", "content_index/content_index.idl",
......
// META: global=worker
// META: script=pako/pako_inflate.min.js
'use strict';
async function compressArrayBuffer(input, format) {
const cs = new CompressionStream(format);
const writer = cs.writable.getWriter();
writer.write(input);
writer.close();
const out = [];
const reader = cs.readable.getReader();
let totalSize = 0;
while (true) {
const { value, done } = await reader.read();
if (done)
break;
out.push(value);
totalSize += value.byteLength;
}
const concatenated = new Uint8Array(totalSize);
let offset = 0;
for (const array of out) {
concatenated.set(array, offset);
offset += array.byteLength;
}
return concatenated;
}
test(() => {
assert_throws(new TypeError(), () => {
const transformer = new CompressionStream("nonvalid");
}, "non supported format should throw");
}, "CompressionStream constructor should throw on invalid format");
promise_test(async () => {
const buffer = new ArrayBuffer(0);
const bufferView = new Uint8Array(buffer);
const compressedData = await compressArrayBuffer(bufferView, "deflate");
// decompress with pako, and check that we got the same result as our original string
assert_array_equals(bufferView, pako.inflate(compressedData));
}, "deflated empty data should be reinflated back to its origin");
promise_test(async () => {
const response = await fetch("/media/foo.vtt")
const buffer = await response.arrayBuffer();
const bufferView = new Uint8Array(buffer);
const compressedData = await compressArrayBuffer(bufferView, "deflate");
// decompress with pako, and check that we got the same result as our original string
assert_array_equals(bufferView, pako.inflate(compressedData));
}, "deflated small amount data should be reinflated back to its origin");
promise_test(async () => {
const response = await fetch("/media/test.mp4")
const buffer = await response.arrayBuffer();
const bufferView = new Uint8Array(buffer);
const compressedData = await compressArrayBuffer(bufferView, "deflate");
// decompress with pako, and check that we got the same result as our original string
assert_array_equals(bufferView, pako.inflate(compressedData));
}, "deflated large amount data should be reinflated back to its origin");
promise_test(async () => {
const buffer = new ArrayBuffer(0);
const bufferView = new Uint8Array(buffer);
const compressedData = await compressArrayBuffer(bufferView, "gzip");
// decompress with pako, and check that we got the same result as our original string
assert_array_equals(bufferView, pako.inflate(compressedData));
}, "gzipped empty data should be reinflated back to its origin");
promise_test(async () => {
const response = await fetch("/media/foo.vtt")
const buffer = await response.arrayBuffer();
const bufferView = new Uint8Array(buffer);
const compressedData = await compressArrayBuffer(bufferView, "gzip");
// decompress with pako, and check that we got the same result as our original string
assert_array_equals(bufferView, pako.inflate(compressedData));
}, "gzipped small amount data should be reinflated back to its origin");
promise_test(async () => {
const response = await fetch("/media/test.mp4")
const buffer = await response.arrayBuffer();
const bufferView = new Uint8Array(buffer);
const compressedData = await compressArrayBuffer(bufferView, "gzip");
// decompress with pako, and check that we got the same result as our original string
assert_array_equals(bufferView, pako.inflate(compressedData));
}, "gzipped large amount data should be reinflated back to its origin");
(The MIT License)
Copyright (C) 2014-2017 by Vitaly Puzrin and Andrei Tuputcyn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
original repository:
https://github.com/nodeca/pako
...@@ -144,6 +144,11 @@ interface CloseEvent : Event ...@@ -144,6 +144,11 @@ interface CloseEvent : Event
getter reason getter reason
getter wasClean getter wasClean
method constructor method constructor
interface CompressionStream
attribute @@toStringTag
getter readable
getter writable
method constructor
interface ContentIndex interface ContentIndex
attribute @@toStringTag attribute @@toStringTag
method add method add
......
...@@ -113,6 +113,11 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -113,6 +113,11 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] getter reason [Worker] getter reason
[Worker] getter wasClean [Worker] getter wasClean
[Worker] method constructor [Worker] method constructor
[Worker] interface CompressionStream
[Worker] attribute @@toStringTag
[Worker] getter readable
[Worker] getter writable
[Worker] method constructor
[Worker] interface ContentIndex [Worker] interface ContentIndex
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] method add [Worker] method add
......
...@@ -1109,6 +1109,11 @@ interface CompositionEvent : UIEvent ...@@ -1109,6 +1109,11 @@ interface CompositionEvent : UIEvent
getter data getter data
method constructor method constructor
method initCompositionEvent method initCompositionEvent
interface CompressionStream
attribute @@toStringTag
getter readable
getter writable
method constructor
interface ComputedAccessibleNode interface ComputedAccessibleNode
attribute @@toStringTag attribute @@toStringTag
getter atomic getter atomic
......
...@@ -113,6 +113,11 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -113,6 +113,11 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] getter reason [Worker] getter reason
[Worker] getter wasClean [Worker] getter wasClean
[Worker] method constructor [Worker] method constructor
[Worker] interface CompressionStream
[Worker] attribute @@toStringTag
[Worker] getter readable
[Worker] getter writable
[Worker] method constructor
[Worker] interface ContentIndex [Worker] interface ContentIndex
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] method add [Worker] method add
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment