Current section
Files
Jump to
Current section
Files
lib/quickbeam/vm/runtime/web/compression.ex
defmodule QuickBEAM.VM.Runtime.Web.Compression do
@moduledoc "compression global object and CompressionStream/DecompressionStream for BEAM mode."
@behaviour QuickBEAM.VM.Runtime.BindingProvider
import QuickBEAM.VM.Builtin, only: [arg: 3, argv: 2, object: 1]
alias QuickBEAM.VM.{Heap, JSThrow}
alias QuickBEAM.VM.Runtime.Web.BinaryData
alias QuickBEAM.VM.Runtime.Web.Buffer
alias QuickBEAM.VM.Runtime.Web.IteratorResult
alias QuickBEAM.VM.Runtime.WebAPIs
@doc "Returns the JavaScript global bindings provided by this module."
def bindings do
%{
"compression" => build_compression_global(),
"CompressionStream" => WebAPIs.register("CompressionStream", &build_compression_stream/2),
"DecompressionStream" =>
WebAPIs.register("DecompressionStream", &build_decompression_stream/2)
}
end
defp build_compression_global do
object do
method "compress" do
[format, data] = argv(args, [nil, nil])
format_str = to_string(format)
validate_format!(format_str)
bytes = Buffer.extract_buf_bytes(data)
result =
try do
QuickBEAM.Compression.compress([format_str, bytes])
rescue
e -> JSThrow.type_error!(Exception.message(e))
end
bytes_to_uint8(result)
end
method "decompress" do
[format, data] = argv(args, [nil, nil])
format_str = to_string(format)
validate_format!(format_str)
bytes = Buffer.extract_buf_bytes(data)
result =
try do
QuickBEAM.Compression.decompress([format_str, bytes])
rescue
e -> JSThrow.type_error!(Exception.message(e))
end
bytes_to_uint8(result)
end
end
end
defp build_compression_stream([format | _], _this) do
format_str = to_string(format)
validate_format!(format_str)
chunks_ref = make_ref()
Heap.put_obj(chunks_ref, %{chunks: [], closed: false})
controller = build_transform_controller(chunks_ref, format_str, :compress)
{readable, writable} = build_transform_streams(chunks_ref, format_str, :compress, controller)
Heap.wrap(%{"readable" => readable, "writable" => writable})
end
defp build_compression_stream([], _this) do
JSThrow.type_error!("CompressionStream requires a format argument")
end
defp build_decompression_stream([format | _], _this) do
format_str = to_string(format)
validate_format!(format_str)
chunks_ref = make_ref()
Heap.put_obj(chunks_ref, %{chunks: [], closed: false})
controller = build_transform_controller(chunks_ref, format_str, :decompress)
{readable, writable} =
build_transform_streams(chunks_ref, format_str, :decompress, controller)
Heap.wrap(%{"readable" => readable, "writable" => writable})
end
defp build_decompression_stream([], _this) do
JSThrow.type_error!("DecompressionStream requires a format argument")
end
defp build_transform_controller(chunks_ref, _format, _op) do
Heap.wrap(%{
"enqueue" =>
{:builtin, "enqueue",
fn [chunk | _], _ ->
state = Heap.get_obj(chunks_ref, %{})
chunks = Map.get(state, :chunks, [])
Heap.put_obj(chunks_ref, Map.put(state, :chunks, chunks ++ [chunk]))
:undefined
end},
"terminate" =>
{:builtin, "terminate",
fn _, _ ->
state = Heap.get_obj(chunks_ref, %{})
Heap.put_obj(chunks_ref, Map.put(state, :closed, true))
:undefined
end}
})
end
defp build_transform_streams(chunks_ref, format, op, _controller) do
alias QuickBEAM.VM.PromiseState
writable =
object do
method "getWriter" do
object do
method "write" do
chunk = arg(args, 0, nil)
bytes = Buffer.extract_buf_bytes(chunk)
transformed =
case op do
:compress ->
{:bytes, b} = QuickBEAM.Compression.compress([format, bytes])
b
:decompress ->
{:bytes, b} = QuickBEAM.Compression.decompress([format, bytes])
b
end
state = Heap.get_obj(chunks_ref, %{})
existing = Map.get(state, :chunks, [])
Heap.put_obj(chunks_ref, Map.put(state, :chunks, existing ++ [transformed]))
PromiseState.resolved(:undefined)
end
method "close" do
state = Heap.get_obj(chunks_ref, %{})
Heap.put_obj(chunks_ref, Map.put(state, :closed, true))
PromiseState.resolved(:undefined)
end
method "abort" do
PromiseState.resolved(:undefined)
end
method "releaseLock" do
:undefined
end
end
end
method "abort" do
PromiseState.resolved(:undefined)
end
end
readable =
object do
method "getReader" do
object do
method "read" do
state = Heap.get_obj(chunks_ref, %{})
chunks = Map.get(state, :chunks, [])
case chunks do
[chunk | rest] ->
Heap.put_obj(chunks_ref, Map.put(state, :chunks, rest))
val =
case chunk do
b when is_binary(b) -> bytes_to_uint8({:bytes, b})
_ -> chunk
end
IteratorResult.resolved_value(val)
[] ->
if Map.get(state, :closed, false) do
IteratorResult.resolved_done()
else
IteratorResult.resolved_done()
end
end
end
method "releaseLock" do
:undefined
end
method "cancel" do
PromiseState.resolved(:undefined)
end
end
end
end
{readable, writable}
end
defp validate_format!("gzip"), do: :ok
defp validate_format!("deflate"), do: :ok
defp validate_format!("deflate-raw"), do: :ok
defp validate_format!(fmt), do: JSThrow.type_error!("Unsupported compression format: #{fmt}")
defp bytes_to_uint8({:bytes, bytes}), do: bytes_to_uint8(bytes)
defp bytes_to_uint8(bytes) when is_binary(bytes), do: BinaryData.uint8_array(bytes)
end