-
-
Notifications
You must be signed in to change notification settings - Fork 969
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LibWeb: Add CompressionStream/DecompressionStream
TODO: Figure out how to make JS APIs private TODO: Bundle JS inside the executable TODO: Create a proper test case TODO: Figure out how to run WPT
- Loading branch information
Showing
15 changed files
with
438 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
Userland/Libraries/LibWeb/Compression/CompressionStream.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/* | ||
* Copyright (c) 2024, Johan Dahlin <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <LibCompress/Deflate.h> | ||
#include <LibCompress/Gzip.h> | ||
#include <LibCompress/Zlib.h> | ||
#include <LibJS/Bytecode/Interpreter.h> | ||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h> | ||
#include <LibJS/Runtime/Realm.h> | ||
#include <LibJS/Runtime/TypedArray.h> | ||
#include <LibJS/Runtime/VM.h> | ||
#include <LibWeb/Bindings/CompressionStreamPrototype.h> | ||
#include <LibWeb/Bindings/Intrinsics.h> | ||
#include <LibWeb/Bindings/PlatformObject.h> | ||
#include <LibWeb/Compression/CompressionStream.h> | ||
#include <LibWeb/WebIDL/AbstractOperations.h> | ||
#include <LibWeb/WebIDL/Buffers.h> | ||
#include <LibWeb/WebIDL/ExceptionOr.h> | ||
|
||
namespace Web::Compression { | ||
|
||
JS_DEFINE_ALLOCATOR(CompressionStream); | ||
|
||
WebIDL::ExceptionOr<JS::NonnullGCPtr<CompressionStream>> CompressionStream::construct_impl(JS::Realm& realm, Bindings::CompressionFormat format) | ||
{ | ||
return realm.heap().allocate<CompressionStream>(realm, realm, format); | ||
} | ||
|
||
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Uint8Array>> CompressionStream::compress(JS::VM& vm, Bindings::CompressionFormat format, JS::Handle<WebIDL::BufferSource> buffer_source) | ||
{ | ||
auto realm = vm.current_realm(); | ||
auto data_buffer_or_error = WebIDL::get_buffer_source_copy(*buffer_source->raw_object()); | ||
if (data_buffer_or_error.is_error()) | ||
return WebIDL::OperationError::create(*realm, "Failed to copy bytes from ArrayBuffer"_fly_string); | ||
|
||
ByteBuffer const& data_buffer = data_buffer_or_error.value(); | ||
Optional<ErrorOr<ByteBuffer>> compressed; | ||
|
||
if (format == Bindings::CompressionFormat::Deflate) { | ||
compressed = Compress::ZlibCompressor::compress_all(data_buffer); | ||
} else if (format == Bindings::CompressionFormat::Gzip) { | ||
compressed = Compress::GzipCompressor::compress_all(data_buffer); | ||
} else if (format == Bindings::CompressionFormat::DeflateRaw) { | ||
compressed = Compress::DeflateCompressor::compress_all(data_buffer); | ||
} else { | ||
return WebIDL::SimpleException { | ||
WebIDL::SimpleExceptionType::TypeError, | ||
"Invalid compression format"sv | ||
}; | ||
} | ||
|
||
if (compressed.value().is_error()) | ||
return WebIDL::OperationError::create(*realm, "Failed to compress data"_fly_string); | ||
|
||
auto compressed_data = compressed.value().release_value(); | ||
auto array_buffer = JS::ArrayBuffer::create(*realm, compressed_data); | ||
return JS::Uint8Array::create(*realm, array_buffer->byte_length(), *array_buffer); | ||
} | ||
|
||
static JS::GCPtr<JS::Script> import_js_script(JS::Realm& realm) | ||
{ | ||
auto& vm = realm.vm(); | ||
auto file = MUST(Core::File::open("Userland/Libraries/LibWeb/Compression/CompressionStream.js"sv, Core::File::OpenMode::Read)); | ||
auto file_contents = MUST(file->read_until_eof()); | ||
auto source = StringView { file_contents }; | ||
|
||
auto script = MUST(JS::Script::parse(source, realm, "CompressionStream.js"sv)); | ||
MUST(vm.bytecode_interpreter().run(*script)); | ||
return script; | ||
} | ||
|
||
CompressionStream::CompressionStream(JS::Realm& realm, Bindings::CompressionFormat format) | ||
: Bindings::PlatformObject(realm) | ||
, m_format(format) | ||
, m_js_script(import_js_script(realm)) | ||
, m_this_value(JS::Object::create(realm, realm.intrinsics().object_prototype())) | ||
{ | ||
auto& vm = realm.vm(); | ||
auto* env = vm.variable_environment(); | ||
if (env) { | ||
// FIXME: Make this private to the web execution context | ||
auto constructor_value = MUST(env->get_binding_value(vm, "CompressionStream_constructor", true)); | ||
auto& func = static_cast<JS::ECMAScriptFunctionObject&>(constructor_value.as_function()); | ||
JS::MarkedVector<JS::Value> arguments_list { vm.heap() }; | ||
arguments_list.append(JS::PrimitiveString::create(vm, Bindings::idl_enum_to_string(format))); | ||
MUST(func.internal_call(m_this_value, move(arguments_list))); | ||
} | ||
} | ||
|
||
CompressionStream::~CompressionStream() = default; | ||
|
||
void CompressionStream::initialize(JS::Realm& realm) | ||
{ | ||
Base::initialize(realm); | ||
WEB_SET_PROTOTYPE_FOR_INTERFACE(CompressionStream); | ||
} | ||
|
||
void CompressionStream::visit_edges(Cell::Visitor& visitor) | ||
{ | ||
Base::visit_edges(visitor); | ||
visitor.visit(m_js_script); | ||
visitor.visit(m_this_value); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Copyright (c) 2024, Johan Dahlin <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include <LibJS/Script.h> | ||
#include <LibWeb/Bindings/CompressionStreamPrototype.h> | ||
#include <LibWeb/Bindings/PlatformObject.h> | ||
#include <LibWeb/Forward.h> | ||
#include <LibWeb/Streams/ReadableStream.h> | ||
#include <LibWeb/Streams/WritableStream.h> | ||
#include <LibWeb/WebIDL/ExceptionOr.h> | ||
|
||
namespace Web::Compression { | ||
|
||
class CompressionStream final : public Bindings::PlatformObject { | ||
WEB_PLATFORM_OBJECT(CompressionStream, Bindings::PlatformObject); | ||
JS_DECLARE_ALLOCATOR(CompressionStream); | ||
|
||
public: | ||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<CompressionStream>> construct_impl(JS::Realm&, Bindings::CompressionFormat format); | ||
static WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Uint8Array>> compress(JS::VM& vm, Bindings::CompressionFormat format, JS::Handle<WebIDL::BufferSource> buffer_source); | ||
virtual ~CompressionStream() override; | ||
|
||
JS::GCPtr<Web::Streams::ReadableStream> readable() const | ||
{ | ||
auto readable = MUST(m_this_value->get(JS::PropertyKey { "readable" })); | ||
return verify_cast<Web::Streams::ReadableStream>(readable.as_object()); | ||
} | ||
|
||
JS::GCPtr<Web::Streams::WritableStream> writable() const | ||
{ | ||
auto writable = MUST(m_this_value->get(JS::PropertyKey { "writable" })); | ||
return verify_cast<Web::Streams::WritableStream>(writable.as_object()); | ||
} | ||
|
||
private: | ||
CompressionStream(JS::Realm&, Bindings::CompressionFormat); | ||
virtual void initialize(JS::Realm&) override; | ||
virtual void visit_edges(Cell::Visitor&) override; | ||
|
||
Bindings::CompressionFormat m_format; | ||
JS::GCPtr<JS::Script> m_js_script; | ||
JS::GCPtr<JS::Object> m_this_value; | ||
}; | ||
|
||
} |
18 changes: 18 additions & 0 deletions
18
Userland/Libraries/LibWeb/Compression/CompressionStream.idl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// https://streams.spec.whatwg.org/#generictransformstream | ||
interface mixin GenericTransformStream { | ||
readonly attribute ReadableStream readable; | ||
readonly attribute WritableStream writable; | ||
}; | ||
|
||
// https://compression.spec.whatwg.org/#compression-stream | ||
enum CompressionFormat { "deflate", "deflate-raw", "gzip" }; | ||
|
||
[Exposed=*] | ||
interface CompressionStream { | ||
constructor(CompressionFormat format); | ||
|
||
// Non-standard | ||
static Uint8Array compress(CompressionFormat format, BufferSource data); | ||
|
||
}; | ||
CompressionStream includes GenericTransformStream; |
20 changes: 20 additions & 0 deletions
20
Userland/Libraries/LibWeb/Compression/CompressionStream.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
function CompressionStream_constructor(format) { | ||
this.transform = new TransformStream({ | ||
start(controller) { | ||
controller.temp = []; | ||
return Promise.resolve(); | ||
}, | ||
transform(chunk, controller) { | ||
controller.temp.push(CompressionStream.compress(format, chunk)); | ||
return Promise.resolve(); | ||
}, | ||
flush(controller) { | ||
for (chunk of controller.temp) { | ||
controller.enqueue(chunk); | ||
} | ||
return Promise.resolve(); | ||
}, | ||
}); | ||
this.readable = this.transform.readable; | ||
this.writable = this.transform.writable; | ||
} |
110 changes: 110 additions & 0 deletions
110
Userland/Libraries/LibWeb/Compression/DecompressionStream.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* | ||
* Copyright (c) 2024, Johan Dahlin <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <LibCompress/Deflate.h> | ||
#include <LibCompress/Gzip.h> | ||
#include <LibCompress/Zlib.h> | ||
#include <LibJS/Bytecode/Interpreter.h> | ||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h> | ||
#include <LibJS/Runtime/Realm.h> | ||
#include <LibJS/Runtime/TypedArray.h> | ||
#include <LibJS/Runtime/VM.h> | ||
#include <LibWeb/Bindings/DecompressionStreamPrototype.h> | ||
#include <LibWeb/Bindings/Intrinsics.h> | ||
#include <LibWeb/Bindings/PlatformObject.h> | ||
#include <LibWeb/Compression/DecompressionStream.h> | ||
#include <LibWeb/WebIDL/AbstractOperations.h> | ||
#include <LibWeb/WebIDL/Buffers.h> | ||
#include <LibWeb/WebIDL/ExceptionOr.h> | ||
|
||
namespace Web::Compression { | ||
|
||
JS_DEFINE_ALLOCATOR(DecompressionStream); | ||
|
||
WebIDL::ExceptionOr<JS::NonnullGCPtr<DecompressionStream>> DecompressionStream::construct_impl(JS::Realm& realm, Bindings::CompressionFormat format) | ||
{ | ||
return realm.heap().allocate<DecompressionStream>(realm, realm, format); | ||
} | ||
|
||
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Uint8Array>> DecompressionStream::decompress(JS::VM& vm, Bindings::CompressionFormat format, JS::Handle<WebIDL::BufferSource> buffer_source) | ||
{ | ||
auto realm = vm.current_realm(); | ||
auto data_buffer_or_error = WebIDL::get_buffer_source_copy(*buffer_source->raw_object()); | ||
if (data_buffer_or_error.is_error()) | ||
return WebIDL::OperationError::create(*realm, "Failed to copy bytes from ArrayBuffer"_fly_string); | ||
|
||
ByteBuffer const& data_buffer = data_buffer_or_error.value(); | ||
Optional<ErrorOr<ByteBuffer>> decompressed; | ||
|
||
if (format == Bindings::CompressionFormat::Deflate) { | ||
decompressed = Compress::ZlibDecompressor::decompress_all(data_buffer); | ||
} else if (format == Bindings::CompressionFormat::Gzip) { | ||
decompressed = Compress::GzipDecompressor::decompress_all(data_buffer); | ||
} else if (format == Bindings::CompressionFormat::DeflateRaw) { | ||
decompressed = Compress::DeflateDecompressor::decompress_all(data_buffer); | ||
} else { | ||
return WebIDL::SimpleException { | ||
WebIDL::SimpleExceptionType::TypeError, | ||
"Invalid compression format"sv | ||
}; | ||
} | ||
|
||
if (decompressed.value().is_error()) { | ||
outln("Failed to decompress data: {}", decompressed.value().error()); | ||
return WebIDL::OperationError::create(*realm, "Failed to decompress data"_fly_string); | ||
} | ||
|
||
auto decompressed_data = decompressed.value().release_value(); | ||
auto array_buffer = JS::ArrayBuffer::create(*realm, decompressed_data); | ||
return JS::Uint8Array::create(*realm, array_buffer->byte_length(), *array_buffer); | ||
} | ||
|
||
static JS::GCPtr<JS::Script> import_js_script(JS::Realm& realm) | ||
{ | ||
auto& vm = realm.vm(); | ||
auto file = MUST(Core::File::open("Userland/Libraries/LibWeb/Compression/DecompressionStream.js"sv, Core::File::OpenMode::Read)); | ||
auto file_contents = MUST(file->read_until_eof()); | ||
auto source = StringView { file_contents }; | ||
|
||
auto script = MUST(JS::Script::parse(source, realm, "DecompressionStream.js"sv)); | ||
MUST(vm.bytecode_interpreter().run(*script)); | ||
return script; | ||
} | ||
|
||
DecompressionStream::DecompressionStream(JS::Realm& realm, Bindings::CompressionFormat format) | ||
: Bindings::PlatformObject(realm) | ||
, m_format(format) | ||
, m_js_script(import_js_script(realm)) | ||
, m_this_value(JS::Object::create(realm, realm.intrinsics().object_prototype())) | ||
{ | ||
auto& vm = realm.vm(); | ||
auto* env = vm.variable_environment(); | ||
if (env) { | ||
// FIXME: Make this private to the web execution context | ||
auto constructor_value = MUST(env->get_binding_value(vm, "DecompressionStream_constructor", true)); | ||
auto& func = static_cast<JS::ECMAScriptFunctionObject&>(constructor_value.as_function()); | ||
JS::MarkedVector<JS::Value> arguments_list { vm.heap() }; | ||
arguments_list.append(JS::PrimitiveString::create(vm, Bindings::idl_enum_to_string(format))); | ||
MUST(func.internal_call(m_this_value, move(arguments_list))); | ||
} | ||
} | ||
|
||
DecompressionStream::~DecompressionStream() = default; | ||
|
||
void DecompressionStream::initialize(JS::Realm& realm) | ||
{ | ||
Base::initialize(realm); | ||
WEB_SET_PROTOTYPE_FOR_INTERFACE(DecompressionStream); | ||
} | ||
|
||
void DecompressionStream::visit_edges(Cell::Visitor& visitor) | ||
{ | ||
Base::visit_edges(visitor); | ||
visitor.visit(m_js_script); | ||
visitor.visit(m_this_value); | ||
} | ||
|
||
} |
Oops, something went wrong.