mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
runtime: implement CompressionStream/DecompressionStream (#24757)
Closes https://github.com/oven-sh/bun/issues/1723 Closes https://github.com/oven-sh/bun/pull/22214 Closes https://github.com/oven-sh/bun/pull/24241 also supports the `"brotli"` and `"zstd"` formats <img width="1244" height="547" alt="image" src="https://github.com/user-attachments/assets/aecf4489-29ad-411d-9f6b-3bee50ed1b27" />
This commit is contained in:
@@ -13,7 +13,4 @@ export function run(opts = {}) {
|
||||
}
|
||||
|
||||
export const bench = Mitata.bench;
|
||||
|
||||
export function group(_name, fn) {
|
||||
return Mitata.group(fn);
|
||||
}
|
||||
export const group = Mitata.group;
|
||||
|
||||
156
bench/snippets/compression-streams.mjs
Normal file
156
bench/snippets/compression-streams.mjs
Normal file
@@ -0,0 +1,156 @@
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
const runAll = !process.argv.includes("--simple");
|
||||
|
||||
const small = new Uint8Array(1024);
|
||||
const medium = new Uint8Array(1024 * 100);
|
||||
const large = new Uint8Array(1024 * 1024);
|
||||
|
||||
for (let i = 0; i < large.length; i++) {
|
||||
const value = Math.floor(Math.sin(i / 100) * 128 + 128);
|
||||
if (i < small.length) small[i] = value;
|
||||
if (i < medium.length) medium[i] = value;
|
||||
large[i] = value;
|
||||
}
|
||||
|
||||
const format = new Intl.NumberFormat("en-US", { notation: "compact", unit: "byte" });
|
||||
|
||||
async function compress(data, format) {
|
||||
const cs = new CompressionStream(format);
|
||||
const writer = cs.writable.getWriter();
|
||||
const reader = cs.readable.getReader();
|
||||
|
||||
writer.write(data);
|
||||
writer.close();
|
||||
|
||||
const chunks = [];
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function decompress(data, format) {
|
||||
const ds = new DecompressionStream(format);
|
||||
const writer = ds.writable.getWriter();
|
||||
const reader = ds.readable.getReader();
|
||||
|
||||
writer.write(data);
|
||||
writer.close();
|
||||
|
||||
const chunks = [];
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
result.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function roundTrip(data, format) {
|
||||
const compressed = await compress(data, format);
|
||||
return await decompress(compressed, format);
|
||||
}
|
||||
|
||||
const formats = ["deflate", "gzip", "deflate-raw"];
|
||||
if (runAll) formats.push("brotli", "zstd");
|
||||
|
||||
// Small data benchmarks (1KB)
|
||||
group(`CompressionStream ${format.format(small.length)}`, () => {
|
||||
for (const fmt of formats) {
|
||||
try {
|
||||
new CompressionStream(fmt);
|
||||
bench(fmt, async () => await compress(small, fmt));
|
||||
} catch (e) {
|
||||
// Skip unsupported formats
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Medium data benchmarks (100KB)
|
||||
group(`CompressionStream ${format.format(medium.length)}`, () => {
|
||||
for (const fmt of formats) {
|
||||
try {
|
||||
new CompressionStream(fmt);
|
||||
bench(fmt, async () => await compress(medium, fmt));
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
|
||||
// Large data benchmarks (1MB)
|
||||
group(`CompressionStream ${format.format(large.length)}`, () => {
|
||||
for (const fmt of formats) {
|
||||
try {
|
||||
new CompressionStream(fmt);
|
||||
bench(fmt, async () => await compress(large, fmt));
|
||||
} catch (e) {
|
||||
// Skip unsupported formats
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const compressedData = {};
|
||||
for (const fmt of formats) {
|
||||
try {
|
||||
compressedData[fmt] = {
|
||||
small: await compress(small, fmt),
|
||||
medium: await compress(medium, fmt),
|
||||
large: await compress(large, fmt),
|
||||
};
|
||||
} catch (e) {
|
||||
// Skip unsupported formats
|
||||
}
|
||||
}
|
||||
|
||||
group(`DecompressionStream ${format.format(small.length)}`, () => {
|
||||
for (const fmt of formats) {
|
||||
if (compressedData[fmt]) {
|
||||
bench(fmt, async () => await decompress(compressedData[fmt].small, fmt));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
group(`DecompressionStream ${format.format(medium.length)}`, () => {
|
||||
for (const fmt of formats) {
|
||||
if (compressedData[fmt]) {
|
||||
bench(fmt, async () => await decompress(compressedData[fmt].medium, fmt));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
group(`DecompressionStream ${format.format(large.length)}`, () => {
|
||||
for (const fmt of formats) {
|
||||
if (compressedData[fmt]) {
|
||||
bench(fmt, async () => await decompress(compressedData[fmt].large, fmt));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
group(`roundtrip ${format.format(large.length)}`, () => {
|
||||
for (const fmt of formats) {
|
||||
try {
|
||||
new CompressionStream(fmt);
|
||||
bench(fmt, async () => await roundTrip(large, fmt));
|
||||
} catch (e) {
|
||||
// Skip unsupported formats
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -245,7 +245,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa
|
||||
|
||||
### [`CompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)
|
||||
|
||||
🔴 Not implemented.
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)
|
||||
|
||||
@@ -273,7 +273,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa
|
||||
|
||||
### [`DecompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream)
|
||||
|
||||
🔴 Not implemented.
|
||||
🟢 Fully implemented.
|
||||
|
||||
### [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)
|
||||
|
||||
|
||||
139
src/bun.js/bindings/JSCompressionStream.cpp
Normal file
139
src/bun.js/bindings/JSCompressionStream.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "config.h"
|
||||
#include "JSCompressionStream.h"
|
||||
|
||||
#include "JSDOMBuiltinConstructor.h"
|
||||
#include "JSDOMGlobalObjectInlines.h"
|
||||
#include "WebCoreJSClientData.h"
|
||||
#include "WebCoreJSBuiltins.h"
|
||||
#include <JavaScriptCore/FunctionPrototype.h>
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
static JSC_DECLARE_CUSTOM_GETTER(jsCompressionStreamConstructor);
|
||||
|
||||
class JSCompressionStreamPrototype final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
static JSCompressionStreamPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
|
||||
{
|
||||
JSCompressionStreamPrototype* ptr = new (NotNull, JSC::allocateCell<JSCompressionStreamPrototype>(vm)) JSCompressionStreamPrototype(vm, globalObject, structure);
|
||||
ptr->finishCreation(vm);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
template<typename CellType, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCompressionStreamPrototype, Base);
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
private:
|
||||
JSCompressionStreamPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
|
||||
: JSC::JSNonFinalObject(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
};
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCompressionStreamPrototype, JSCompressionStreamPrototype::Base);
|
||||
|
||||
using JSCompressionStreamDOMConstructor = JSDOMBuiltinConstructor<JSCompressionStream>;
|
||||
|
||||
template<> const ClassInfo JSCompressionStreamDOMConstructor::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStreamDOMConstructor) };
|
||||
|
||||
template<> JSValue JSCompressionStreamDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
UNUSED_PARAM(vm);
|
||||
return globalObject.functionPrototype();
|
||||
}
|
||||
|
||||
template<> void JSCompressionStreamDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
|
||||
JSString* nameString = jsNontrivialString(vm, "CompressionStream"_s);
|
||||
m_originalName.set(vm, this, nameString);
|
||||
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
|
||||
putDirect(vm, vm.propertyNames->prototype, JSCompressionStream::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
|
||||
}
|
||||
|
||||
template<> FunctionExecutable* JSCompressionStreamDOMConstructor::initializeExecutable(VM& vm)
|
||||
{
|
||||
return compressionStreamInitializeCompressionStreamCodeGenerator(vm);
|
||||
}
|
||||
|
||||
static const HashTableValue JSCompressionStreamPrototypeTableValues[] = {
|
||||
{ "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsCompressionStreamConstructor, 0 } },
|
||||
{ "readable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, compressionStreamReadableCodeGenerator, 0 } },
|
||||
{ "writable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, compressionStreamWritableCodeGenerator, 0 } },
|
||||
};
|
||||
|
||||
const ClassInfo JSCompressionStreamPrototype::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStreamPrototype) };
|
||||
|
||||
void JSCompressionStreamPrototype::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
reifyStaticProperties(vm, JSCompressionStream::info(), JSCompressionStreamPrototypeTableValues, *this);
|
||||
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
|
||||
}
|
||||
|
||||
const ClassInfo JSCompressionStream::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStream) };
|
||||
|
||||
JSC::GCClient::IsoSubspace* JSCompressionStream::subspaceForImpl(JSC::VM& vm)
|
||||
{
|
||||
return WebCore::subspaceForImpl<JSCompressionStream, UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForCompressionStream.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCompressionStream = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForCompressionStream.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForCompressionStream = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
JSCompressionStream::JSCompressionStream(Structure* structure, JSDOMGlobalObject& globalObject)
|
||||
: JSDOMObject(structure, globalObject)
|
||||
{
|
||||
}
|
||||
|
||||
void JSCompressionStream::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
ASSERT(inherits(info()));
|
||||
}
|
||||
|
||||
JSObject* JSCompressionStream::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
auto* structure = JSCompressionStreamPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
|
||||
structure->setMayBePrototype(true);
|
||||
return JSCompressionStreamPrototype::create(vm, &globalObject, structure);
|
||||
}
|
||||
|
||||
JSObject* JSCompressionStream::prototype(VM& vm, JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
return getDOMPrototype<JSCompressionStream>(vm, globalObject);
|
||||
}
|
||||
|
||||
JSValue JSCompressionStream::getConstructor(VM& vm, const JSGlobalObject* globalObject)
|
||||
{
|
||||
return getDOMConstructor<JSCompressionStreamDOMConstructor, DOMConstructorID::CompressionStream>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(jsCompressionStreamConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
auto* prototype = jsDynamicCast<JSCompressionStreamPrototype*>(JSValue::decode(thisValue));
|
||||
if (!prototype)
|
||||
return throwVMTypeError(lexicalGlobalObject, throwScope);
|
||||
return JSValue::encode(JSCompressionStream::getConstructor(vm, lexicalGlobalObject));
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
40
src/bun.js/bindings/JSCompressionStream.h
Normal file
40
src/bun.js/bindings/JSCompressionStream.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "JSDOMWrapper.h"
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
class JSCompressionStream : public JSDOMObject {
|
||||
public:
|
||||
using Base = JSDOMObject;
|
||||
static JSCompressionStream* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject)
|
||||
{
|
||||
JSCompressionStream* ptr = new (NotNull, JSC::allocateCell<JSCompressionStream>(globalObject->vm())) JSCompressionStream(structure, *globalObject);
|
||||
ptr->finishCreation(globalObject->vm());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
|
||||
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
|
||||
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return subspaceForImpl(vm);
|
||||
}
|
||||
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
|
||||
|
||||
JSCompressionStream(JSC::Structure*, JSDOMGlobalObject&);
|
||||
void finishCreation(JSC::VM&);
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
139
src/bun.js/bindings/JSDecompressionStream.cpp
Normal file
139
src/bun.js/bindings/JSDecompressionStream.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "config.h"
|
||||
#include "JSDecompressionStream.h"
|
||||
|
||||
#include "JSDOMBuiltinConstructor.h"
|
||||
#include "JSDOMGlobalObjectInlines.h"
|
||||
#include "WebCoreJSClientData.h"
|
||||
#include "WebCoreJSBuiltins.h"
|
||||
#include <JavaScriptCore/FunctionPrototype.h>
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
static JSC_DECLARE_CUSTOM_GETTER(jsDecompressionStreamConstructor);
|
||||
|
||||
class JSDecompressionStreamPrototype final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
static JSDecompressionStreamPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
|
||||
{
|
||||
JSDecompressionStreamPrototype* ptr = new (NotNull, JSC::allocateCell<JSDecompressionStreamPrototype>(vm)) JSDecompressionStreamPrototype(vm, globalObject, structure);
|
||||
ptr->finishCreation(vm);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
template<typename CellType, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDecompressionStreamPrototype, Base);
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
private:
|
||||
JSDecompressionStreamPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
|
||||
: JSC::JSNonFinalObject(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
};
|
||||
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDecompressionStreamPrototype, JSDecompressionStreamPrototype::Base);
|
||||
|
||||
using JSDecompressionStreamDOMConstructor = JSDOMBuiltinConstructor<JSDecompressionStream>;
|
||||
|
||||
template<> const ClassInfo JSDecompressionStreamDOMConstructor::s_info = { "DecompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDecompressionStreamDOMConstructor) };
|
||||
|
||||
template<> JSValue JSDecompressionStreamDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
UNUSED_PARAM(vm);
|
||||
return globalObject.functionPrototype();
|
||||
}
|
||||
|
||||
template<> void JSDecompressionStreamDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
|
||||
JSString* nameString = jsNontrivialString(vm, "DecompressionStream"_s);
|
||||
m_originalName.set(vm, this, nameString);
|
||||
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
|
||||
putDirect(vm, vm.propertyNames->prototype, JSDecompressionStream::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
|
||||
}
|
||||
|
||||
template<> FunctionExecutable* JSDecompressionStreamDOMConstructor::initializeExecutable(VM& vm)
|
||||
{
|
||||
return decompressionStreamInitializeDecompressionStreamCodeGenerator(vm);
|
||||
}
|
||||
|
||||
static const HashTableValue JSDecompressionStreamPrototypeTableValues[] = {
|
||||
{ "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsDecompressionStreamConstructor, 0 } },
|
||||
{ "readable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, decompressionStreamReadableCodeGenerator, 0 } },
|
||||
{ "writable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, decompressionStreamWritableCodeGenerator, 0 } },
|
||||
};
|
||||
|
||||
const ClassInfo JSDecompressionStreamPrototype::s_info = { "DecompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDecompressionStreamPrototype) };
|
||||
|
||||
void JSDecompressionStreamPrototype::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
reifyStaticProperties(vm, JSDecompressionStream::info(), JSDecompressionStreamPrototypeTableValues, *this);
|
||||
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
|
||||
}
|
||||
|
||||
const ClassInfo JSDecompressionStream::s_info = { "DecompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDecompressionStream) };
|
||||
|
||||
JSC::GCClient::IsoSubspace* JSDecompressionStream::subspaceForImpl(JSC::VM& vm)
|
||||
{
|
||||
return WebCore::subspaceForImpl<JSDecompressionStream, UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForDecompressionStream.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForDecompressionStream = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForDecompressionStream.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForDecompressionStream = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
JSDecompressionStream::JSDecompressionStream(Structure* structure, JSDOMGlobalObject& globalObject)
|
||||
: JSDOMObject(structure, globalObject)
|
||||
{
|
||||
}
|
||||
|
||||
void JSDecompressionStream::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
ASSERT(inherits(info()));
|
||||
}
|
||||
|
||||
JSObject* JSDecompressionStream::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
auto* structure = JSDecompressionStreamPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
|
||||
structure->setMayBePrototype(true);
|
||||
return JSDecompressionStreamPrototype::create(vm, &globalObject, structure);
|
||||
}
|
||||
|
||||
JSObject* JSDecompressionStream::prototype(VM& vm, JSDOMGlobalObject& globalObject)
|
||||
{
|
||||
return getDOMPrototype<JSDecompressionStream>(vm, globalObject);
|
||||
}
|
||||
|
||||
JSValue JSDecompressionStream::getConstructor(VM& vm, const JSGlobalObject* globalObject)
|
||||
{
|
||||
return getDOMConstructor<JSDecompressionStreamDOMConstructor, DOMConstructorID::DecompressionStream>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(jsDecompressionStreamConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
auto* prototype = jsDynamicCast<JSDecompressionStreamPrototype*>(JSValue::decode(thisValue));
|
||||
if (!prototype)
|
||||
return throwVMTypeError(lexicalGlobalObject, throwScope);
|
||||
return JSValue::encode(JSDecompressionStream::getConstructor(vm, lexicalGlobalObject));
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
40
src/bun.js/bindings/JSDecompressionStream.h
Normal file
40
src/bun.js/bindings/JSDecompressionStream.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "JSDOMWrapper.h"
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
class JSDecompressionStream : public JSDOMObject {
|
||||
public:
|
||||
using Base = JSDOMObject;
|
||||
static JSDecompressionStream* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject)
|
||||
{
|
||||
JSDecompressionStream* ptr = new (NotNull, JSC::allocateCell<JSDecompressionStream>(globalObject->vm())) JSDecompressionStream(structure, *globalObject);
|
||||
ptr->finishCreation(globalObject->vm());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
|
||||
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
|
||||
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return subspaceForImpl(vm);
|
||||
}
|
||||
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
|
||||
|
||||
JSDecompressionStream(JSC::Structure*, JSDOMGlobalObject&);
|
||||
void finishCreation(JSC::VM&);
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
@@ -78,6 +78,8 @@
|
||||
#include "JSAbortAlgorithm.h"
|
||||
#include "JSAbortController.h"
|
||||
#include "JSAbortSignal.h"
|
||||
#include "JSCompressionStream.h"
|
||||
#include "JSDecompressionStream.h"
|
||||
#include "JSBroadcastChannel.h"
|
||||
#include "JSBuffer.h"
|
||||
#include "JSBufferList.h"
|
||||
@@ -962,9 +964,11 @@ WEBCORE_GENERATED_CONSTRUCTOR_GETTER(AbortSignal);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(BroadcastChannel);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(ByteLengthQueuingStrategy)
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CloseEvent);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CompressionStream);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CountQueuingStrategy)
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CryptoKey);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(CustomEvent);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DecompressionStream);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DOMException);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DOMFormData);
|
||||
WEBCORE_GENERATED_CONSTRUCTOR_GETTER(DOMURL);
|
||||
|
||||
@@ -48,9 +48,11 @@
|
||||
BroadcastChannel BroadcastChannelConstructorCallback PropertyCallback
|
||||
ByteLengthQueuingStrategy ByteLengthQueuingStrategyConstructorCallback PropertyCallback
|
||||
CloseEvent CloseEventConstructorCallback PropertyCallback
|
||||
CompressionStream CompressionStreamConstructorCallback PropertyCallback
|
||||
CountQueuingStrategy CountQueuingStrategyConstructorCallback PropertyCallback
|
||||
CryptoKey CryptoKeyConstructorCallback PropertyCallback
|
||||
CustomEvent CustomEventConstructorCallback PropertyCallback
|
||||
DecompressionStream DecompressionStreamConstructorCallback PropertyCallback
|
||||
DOMException DOMExceptionConstructorCallback PropertyCallback
|
||||
ErrorEvent ErrorEventConstructorCallback PropertyCallback
|
||||
Event EventConstructorCallback PropertyCallback
|
||||
|
||||
@@ -8,4 +8,6 @@ export default [
|
||||
["WritableStream", "JSWritableStream.h"],
|
||||
["TransformStream", "JSTransformStream.h"],
|
||||
["ArrayBuffer"],
|
||||
["CompressionStream", "JSCompressionStream.h"],
|
||||
["DecompressionStream", "JSDecompressionStream.h"],
|
||||
];
|
||||
|
||||
@@ -283,6 +283,8 @@ public:
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForReadableStreamSource;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTransformStream;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForTransformStreamDefaultController;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForCompressionStream;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForDecompressionStream;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWritableStream;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWritableStreamDefaultController;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForWritableStreamDefaultWriter;
|
||||
|
||||
@@ -198,6 +198,8 @@ enum class DOMConstructorID : uint16_t {
|
||||
ReadableStreamSource,
|
||||
TransformStream,
|
||||
TransformStreamDefaultController,
|
||||
CompressionStream,
|
||||
DecompressionStream,
|
||||
WritableStream,
|
||||
WritableStreamDefaultController,
|
||||
WritableStreamDefaultWriter,
|
||||
@@ -860,7 +862,7 @@ enum class DOMConstructorID : uint16_t {
|
||||
EventEmitter,
|
||||
};
|
||||
|
||||
static constexpr unsigned numberOfDOMConstructorsBase = 846;
|
||||
static constexpr unsigned numberOfDOMConstructorsBase = 848;
|
||||
|
||||
static constexpr unsigned bunExtraConstructors = 3;
|
||||
|
||||
|
||||
@@ -266,6 +266,8 @@ public:
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForReadableStreamSource;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForTransformStream;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForTransformStreamDefaultController;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForCompressionStream;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForDecompressionStream;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForWritableStream;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForWritableStreamDefaultController;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForWritableStreamDefaultWriter;
|
||||
|
||||
@@ -532,7 +532,7 @@ declare module "module" {
|
||||
`;
|
||||
|
||||
for (const [name] of jsclasses) {
|
||||
dts += `\ndeclare function $inherits${name}(value: any): boolean;`;
|
||||
dts += `\ndeclare function $inherits${name}(value: any): value is ${name};`;
|
||||
}
|
||||
|
||||
return dts;
|
||||
|
||||
33
src/js/builtins/CompressionStream.ts
Normal file
33
src/js/builtins/CompressionStream.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export function initializeCompressionStream(this, format) {
|
||||
const zlib = require("node:zlib");
|
||||
const stream = require("node:stream");
|
||||
|
||||
const builders = {
|
||||
"deflate": zlib.createDeflate,
|
||||
"deflate-raw": zlib.createDeflateRaw,
|
||||
"gzip": zlib.createGzip,
|
||||
"brotli": zlib.createBrotliCompress,
|
||||
"zstd": zlib.createZstdCompress,
|
||||
};
|
||||
|
||||
if (!(format in builders))
|
||||
throw $ERR_INVALID_ARG_VALUE("format", format, "must be one of: " + Object.keys(builders).join(", "));
|
||||
|
||||
const handle = builders[format]();
|
||||
$putByIdDirectPrivate(this, "readable", stream.Readable.toWeb(handle));
|
||||
$putByIdDirectPrivate(this, "writable", stream.Writable.toWeb(handle));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
$getter;
|
||||
export function readable(this) {
|
||||
if (!$inheritsCompressionStream(this)) throw $makeGetterTypeError("CompressionStream", "readable");
|
||||
return $getByIdDirectPrivate(this, "readable");
|
||||
}
|
||||
|
||||
$getter;
|
||||
export function writable(this) {
|
||||
if (!$inheritsCompressionStream(this)) throw $makeGetterTypeError("CompressionStream", "writable");
|
||||
return $getByIdDirectPrivate(this, "writable");
|
||||
}
|
||||
33
src/js/builtins/DecompressionStream.ts
Normal file
33
src/js/builtins/DecompressionStream.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export function initializeDecompressionStream(this, format) {
|
||||
const zlib = require("node:zlib");
|
||||
const stream = require("node:stream");
|
||||
|
||||
const builders = {
|
||||
"deflate": zlib.createInflate,
|
||||
"deflate-raw": zlib.createInflateRaw,
|
||||
"gzip": zlib.createGunzip,
|
||||
"brotli": zlib.createBrotliDecompress,
|
||||
"zstd": zlib.createZstdDecompress,
|
||||
};
|
||||
|
||||
if (!(format in builders))
|
||||
throw $ERR_INVALID_ARG_VALUE("format", format, "must be one of: " + Object.keys(builders).join(", "));
|
||||
|
||||
const handle = builders[format]();
|
||||
$putByIdDirectPrivate(this, "readable", stream.Readable.toWeb(handle));
|
||||
$putByIdDirectPrivate(this, "writable", stream.Writable.toWeb(handle));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
$getter;
|
||||
export function readable(this) {
|
||||
if (!$inheritsDecompressionStream(this)) throw $makeGetterTypeError("DecompressionStream", "readable");
|
||||
return $getByIdDirectPrivate(this, "readable");
|
||||
}
|
||||
|
||||
$getter;
|
||||
export function writable(this) {
|
||||
if (!$inheritsDecompressionStream(this)) throw $makeGetterTypeError("DecompressionStream", "writable");
|
||||
return $getByIdDirectPrivate(this, "writable");
|
||||
}
|
||||
@@ -15,6 +15,6 @@ export default {
|
||||
CountQueuingStrategy,
|
||||
TextEncoderStream,
|
||||
TextDecoderStream,
|
||||
CompressionStream: undefined,
|
||||
DecompressionStream: undefined,
|
||||
CompressionStream,
|
||||
DecompressionStream,
|
||||
};
|
||||
|
||||
@@ -59,6 +59,8 @@ expectType(node_stream.blob()).is<Promise<Blob>>();
|
||||
|
||||
Bun.file("./foo.csv").stream().pipeThrough(new TextDecoderStream()).pipeThrough(new TextEncoderStream());
|
||||
|
||||
Bun.file("./foo.csv").stream().pipeThrough(new CompressionStream("gzip")).pipeThrough(new DecompressionStream("gzip"));
|
||||
|
||||
Bun.file("./foo.csv")
|
||||
.stream()
|
||||
.pipeThrough(new TextDecoderStream())
|
||||
|
||||
24
test/js/node/test/parallel/test-global-webstreams.js
Normal file
24
test/js/node/test/parallel/test-global-webstreams.js
Normal file
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const webstreams = require('stream/web');
|
||||
|
||||
assert.strictEqual(ReadableStream, webstreams.ReadableStream);
|
||||
assert.strictEqual(ReadableStreamDefaultReader, webstreams.ReadableStreamDefaultReader);
|
||||
assert.strictEqual(ReadableStreamBYOBReader, webstreams.ReadableStreamBYOBReader);
|
||||
assert.strictEqual(ReadableStreamBYOBRequest, webstreams.ReadableStreamBYOBRequest);
|
||||
assert.strictEqual(ReadableByteStreamController, webstreams.ReadableByteStreamController);
|
||||
assert.strictEqual(ReadableStreamDefaultController, webstreams.ReadableStreamDefaultController);
|
||||
assert.strictEqual(TransformStream, webstreams.TransformStream);
|
||||
assert.strictEqual(TransformStreamDefaultController, webstreams.TransformStreamDefaultController);
|
||||
assert.strictEqual(WritableStream, webstreams.WritableStream);
|
||||
assert.strictEqual(WritableStreamDefaultWriter, webstreams.WritableStreamDefaultWriter);
|
||||
assert.strictEqual(WritableStreamDefaultController, webstreams.WritableStreamDefaultController);
|
||||
assert.strictEqual(ByteLengthQueuingStrategy, webstreams.ByteLengthQueuingStrategy);
|
||||
assert.strictEqual(CountQueuingStrategy, webstreams.CountQueuingStrategy);
|
||||
assert.strictEqual(TextEncoderStream, webstreams.TextEncoderStream);
|
||||
assert.strictEqual(TextDecoderStream, webstreams.TextDecoderStream);
|
||||
assert.strictEqual(CompressionStream, webstreams.CompressionStream);
|
||||
assert.strictEqual(DecompressionStream, webstreams.DecompressionStream);
|
||||
@@ -0,0 +1,70 @@
|
||||
// Flags: --no-warnings
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const {
|
||||
CompressionStream,
|
||||
DecompressionStream,
|
||||
} = require('stream/web');
|
||||
|
||||
const assert = require('assert');
|
||||
const dec = new TextDecoder();
|
||||
|
||||
async function test(format) {
|
||||
const gzip = new CompressionStream(format);
|
||||
const gunzip = new DecompressionStream(format);
|
||||
|
||||
assert.strictEqual(gzip[Symbol.toStringTag], 'CompressionStream');
|
||||
assert.strictEqual(gunzip[Symbol.toStringTag], 'DecompressionStream');
|
||||
|
||||
gzip.readable.pipeTo(gunzip.writable).then(common.mustCall());
|
||||
|
||||
const reader = gunzip.readable.getReader();
|
||||
const writer = gzip.writable.getWriter();
|
||||
|
||||
const compressed_data = [];
|
||||
const reader_function = ({ value, done }) => {
|
||||
if (value)
|
||||
compressed_data.push(value);
|
||||
if (!done)
|
||||
return reader.read().then(reader_function);
|
||||
assert.strictEqual(dec.decode(Buffer.concat(compressed_data)), 'hello');
|
||||
};
|
||||
const reader_promise = reader.read().then(reader_function);
|
||||
|
||||
await Promise.all([
|
||||
reader_promise,
|
||||
reader_promise.then(() => reader.read().then(({ done }) => assert(done))),
|
||||
writer.write('hello'),
|
||||
writer.close(),
|
||||
]);
|
||||
}
|
||||
|
||||
Promise.all(['gzip', 'deflate', 'deflate-raw', 'brotli', 'zstd'].map((i) => test(i))).then(common.mustCall());
|
||||
|
||||
[1, 'hello', false, {}].forEach((i) => {
|
||||
assert.throws(() => new CompressionStream(i), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
assert.throws(() => new DecompressionStream(i), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => Reflect.get(CompressionStream.prototype, 'readable', {}), {
|
||||
name: 'TypeError',
|
||||
});
|
||||
assert.throws(
|
||||
() => Reflect.get(CompressionStream.prototype, 'writable', {}), {
|
||||
name: 'TypeError',
|
||||
});
|
||||
assert.throws(
|
||||
() => Reflect.get(DecompressionStream.prototype, 'readable', {}), {
|
||||
name: 'TypeError',
|
||||
});
|
||||
assert.throws(
|
||||
() => Reflect.get(DecompressionStream.prototype, 'writable', {}), {
|
||||
name: 'TypeError',
|
||||
});
|
||||
Reference in New Issue
Block a user