aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/scripts')
-rw-r--r--src/bun.js/scripts/class-definitions.ts36
-rw-r--r--src/bun.js/scripts/generate-classes.ts1271
-rw-r--r--src/bun.js/scripts/generate-jssink.js940
3 files changed, 2247 insertions, 0 deletions
diff --git a/src/bun.js/scripts/class-definitions.ts b/src/bun.js/scripts/class-definitions.ts
new file mode 100644
index 000000000..4b202f8a5
--- /dev/null
+++ b/src/bun.js/scripts/class-definitions.ts
@@ -0,0 +1,36 @@
+export type Field =
+ | { getter: string; cache?: true }
+ | { setter: string }
+ | { accessor: { getter: string; setter: string }; cache?: true }
+ | {
+ fn: string;
+ length?: number;
+ DOMJIT?: {
+ return: string;
+ args?: [string, string] | [string, string, string] | [string];
+ symbol: string;
+ };
+ };
+
+export interface ClassDefinition {
+ name: string;
+ construct?: boolean;
+ finalize?: boolean;
+ klass: Record<string, Field>;
+ proto: Record<string, Field>;
+ JSType?: string;
+}
+
+export function define(
+ { klass = {}, proto = {}, ...rest } = {} as ClassDefinition
+): ClassDefinition {
+ return {
+ ...rest,
+ klass: Object.fromEntries(
+ Object.entries(klass).sort(([a], [b]) => a.localeCompare(b))
+ ),
+ proto: Object.fromEntries(
+ Object.entries(proto).sort(([a], [b]) => a.localeCompare(b))
+ ),
+ };
+}
diff --git a/src/bun.js/scripts/generate-classes.ts b/src/bun.js/scripts/generate-classes.ts
new file mode 100644
index 000000000..e8d4fd144
--- /dev/null
+++ b/src/bun.js/scripts/generate-classes.ts
@@ -0,0 +1,1271 @@
+import { readdirSync } from "fs";
+import { resolve } from "path";
+import type { Field, ClassDefinition } from "./class-definitions";
+
+function symbolName(typeName, name) {
+ return `${typeName}__${name}`;
+}
+
+function protoSymbolName(typeName, name) {
+ return `${typeName}Prototype__${name}`;
+}
+
+function classSymbolName(typeName, name) {
+ return `${typeName}Class__${name}`;
+}
+
+function subspaceFor(typeName) {
+ return `m_subspaceFor${typeName}`;
+}
+
+function clientSubspaceFor(typeName) {
+ return `m_clientSubspaceFor${typeName}`;
+}
+
+function prototypeName(typeName) {
+ return `JS${typeName}Prototype`;
+}
+
+function className(typeName) {
+ return `JS${typeName}`;
+}
+
+function constructorName(typeName) {
+ return `JS${typeName}Constructor`;
+}
+
+function appendSymbols(to, symbolName, prop) {
+ var { defaultValue, getter, setter, accesosr, fn } = prop;
+
+ if (accesosr) {
+ getter = accesosr.getter;
+ setter = accesosr.setter;
+ }
+
+ if (getter) {
+ to.push([getter, symbolName(getter)]);
+ }
+
+ if (setter) {
+ to.push([setter, symbolName(setter)]);
+ }
+
+ if (fn) {
+ to.push([fn, symbolName(fn)]);
+ }
+}
+function propRow(
+ symbolName: (a: string, b: string) => string,
+ typeName: string,
+ name: string,
+ prop: Field,
+ isWrapped = true
+) {
+ var {
+ defaultValue,
+ getter,
+ setter,
+ fn,
+ accesosr,
+ fn,
+ length = 0,
+ cache,
+ } = prop;
+
+ if (accesosr) {
+ getter = accesosr.getter;
+ setter = accesosr.setter;
+ }
+
+ var symbol = symbolName(typeName, name);
+
+ if (isWrapped) {
+ if (getter) {
+ getter = symbol + "GetterWrap";
+ }
+ if (setter) {
+ setter = symbol + "SetterWrap";
+ }
+ if (fn) {
+ fn = symbol + "Callback";
+ }
+ } else {
+ if (getter) {
+ getter = symbolName(typeName, getter);
+ }
+ if (setter) {
+ setter = symbolName(typeName, setter);
+ }
+ if (fn) {
+ fn = symbolName(typeName, fn);
+ }
+ }
+
+ if (fn !== undefined) {
+ return `
+{ "${name}"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(${fn}), (intptr_t)(${
+ length || 0
+ }) } }
+`.trim();
+ } else if (getter && setter) {
+ return `
+
+{ "${name}"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(${getter}), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(${setter}) } }
+`.trim();
+ } else if (defaultValue) {
+ } else if (getter) {
+ return `{ "${name}"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(${getter}), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } }
+`.trim();
+ } else if (setter) {
+ return `{ "${name}"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(0), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(${setter}) } }
+ `.trim();
+ }
+
+ throw "Unsupported property";
+}
+
+export function generateHashTable(
+ nameToUse,
+ symbolName,
+ typeName,
+ obj,
+ props = {},
+ wrapped
+) {
+ const rows = [];
+
+ for (const name in props) {
+ rows.push(propRow(symbolName, typeName, name, props[name], wrapped));
+ }
+
+ // static const HashTableValue JSWebSocketPrototypeTableValues[] = {
+ // { "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocketConstructor), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+ // { "URL"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_URL), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+ // { "url"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_url), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+ // { "readyState"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_readyState), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+ // { "bufferedAmount"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_bufferedAmount), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+ // { "onopen"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onopen), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onopen) } },
+ // { "onmessage"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onmessage), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onmessage) } },
+ // { "onerror"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onerror), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onerror) } },
+ // { "onclose"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_onclose), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_onclose) } },
+ // { "protocol"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_protocol), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+ // { "extensions"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_extensions), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+ // { "binaryType"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { (intptr_t) static_cast<PropertySlot::GetValueFunc>(jsWebSocket_binaryType), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSWebSocket_binaryType) } },
+ // { "send"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsWebSocketPrototypeFunction_send), (intptr_t)(1) } },
+ // { "close"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsWebSocketPrototypeFunction_close), (intptr_t)(0) } },
+ // { "CONNECTING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(0) } },
+ // { "OPEN"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(1) } },
+ // { "CLOSING"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(2) } },
+ // { "CLOSED"_s, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::ConstantInteger, NoIntrinsic, { (long long)(3) } },
+ // };
+ return `
+ static const HashTableValue ${nameToUse}TableValues[] = {
+${rows.join(" ,\n")}
+ };
+`;
+}
+
+function generatePrototype(typeName, obj) {
+ const proto = prototypeName(typeName);
+ const { proto: protoFields } = obj;
+ return `
+${
+ "construct" in obj
+ ? `extern "C" void* ${classSymbolName(
+ typeName,
+ "construct"
+ )}(JSC::JSGlobalObject*, JSC::CallFrame*);
+JSC_DECLARE_CUSTOM_GETTER(js${typeName}Constructor);`
+ : ""
+}
+${
+ "finalize" in obj
+ ? `extern "C" void ${classSymbolName(typeName, "finalize")}(void*);`
+ : ""
+}
+
+${renderDecls(protoSymbolName, typeName, protoFields)}
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(${proto}, ${proto}::Base);
+
+${generateHashTable(
+ prototypeName(typeName),
+ protoSymbolName,
+ typeName,
+ obj,
+
+ protoFields,
+ true
+)}
+
+const ClassInfo ${proto}::s_info = { "${typeName}"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${proto}) };
+
+${renderFieldsImpl(protoSymbolName, typeName, obj, protoFields)}
+
+void ${proto}::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+ reifyStaticProperties(vm, ${className(
+ typeName
+ )}::info(), ${proto}TableValues, *this);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+
+
+`;
+}
+
+function generateConstructorHeader(typeName) {
+ const name = constructorName(typeName);
+ const proto = prototypeName(typeName);
+
+ return `
+ class ${proto} final : public JSC::JSNonFinalObject {
+ public:
+ using Base = JSC::JSNonFinalObject;
+
+ static ${proto}* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ ${proto}* ptr = new (NotNull, JSC::allocateCell<${proto}>(vm)) ${proto}(vm, globalObject, structure);
+ ptr->finishCreation(vm, globalObject);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ 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:
+ ${proto}(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+ };
+
+ class ${name} final : public JSC::InternalFunction {
+ public:
+ using Base = JSC::InternalFunction;
+ static ${name}* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, ${prototypeName(
+ typeName
+ )}* prototype);
+
+ static constexpr unsigned StructureFlags = Base::StructureFlags;
+ static constexpr bool needsDestruction = false;
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
+ }
+
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<${name}, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.${clientSubspaceFor(
+ typeName
+ )}Constructor.get(); },
+ [](auto& spaces, auto&& space) { spaces.${clientSubspaceFor(
+ typeName
+ )}Constructor = WTFMove(space); },
+ [](auto& spaces) { return spaces.${subspaceFor(
+ typeName
+ )}Constructor.get(); },
+ [](auto& spaces, auto&& space) { spaces.${subspaceFor(
+ typeName
+ )}Constructor = WTFMove(space); });
+ }
+
+
+ void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, ${prototypeName(
+ typeName
+ )}* prototype);
+
+ // Must be defined for each specialization class.
+ static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
+ DECLARE_EXPORT_INFO;
+ private:
+ ${name}(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction)
+ : Base(vm, structure, nativeFunction, nativeFunction)
+
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, ${prototypeName(
+ typeName
+ )}* prototype);
+ };
+
+ `;
+}
+
+function generateConstructorImpl(typeName, obj) {
+ const name = constructorName(typeName);
+ const { klass: fields } = obj;
+ const hashTable =
+ Object.keys(fields).length > 0
+ ? generateHashTable(name, classSymbolName, typeName, obj, fields, false)
+ : "";
+
+ const hashTableIdentifier = hashTable.length ? `${name}TableValues` : "";
+ return `
+${renderStaticDecls(classSymbolName, typeName, fields)}
+${hashTable}
+
+void ${name}::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, ${prototypeName(
+ typeName
+ )}* prototype)
+{
+ Base::finishCreation(vm, 0, "${typeName}"_s, PropertyAdditionMode::WithoutStructureTransition);
+ ${
+ hashTableIdentifier.length
+ ? `reifyStaticProperties(vm, &${name}::s_info, ${hashTableIdentifier}, *this);`
+ : ""
+ }
+ ASSERT(inherits(info()));
+}
+
+${name}* ${name}::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, ${prototypeName(
+ typeName
+ )}* prototype) {
+ ${name}* ptr = new (NotNull, JSC::allocateCell<${name}>(vm)) ${name}(vm, structure, construct);
+ ptr->finishCreation(vm, globalObject, prototype);
+ return ptr;
+}
+
+JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${name}::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame)
+{
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ JSC::VM &vm = globalObject->vm();
+ JSObject* newTarget = asObject(callFrame->newTarget());
+ auto* constructor = globalObject->${className(typeName)}Constructor();
+ Structure* structure = globalObject->${className(typeName)}Structure();
+ if (constructor != newTarget) {
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ auto* functionGlobalObject = reinterpret_cast<Zig::GlobalObject*>(
+ // ShadowRealm functions belong to a different global object.
+ getFunctionRealm(globalObject, newTarget)
+ );
+ RETURN_IF_EXCEPTION(scope, {});
+ structure = InternalFunction::createSubclassStructure(
+ globalObject,
+ newTarget,
+ functionGlobalObject->${className(typeName)}Structure()
+ );
+ }
+
+ void* ptr = ${classSymbolName(
+ typeName,
+ "construct"
+ )}(globalObject, callFrame);
+
+ if (UNLIKELY(!ptr)) {
+ return JSValue::encode(JSC::jsUndefined());
+ }
+
+ ${className(typeName)}* instance = ${className(
+ typeName
+ )}::create(vm, globalObject, structure, ptr);
+
+ return JSValue::encode(instance);
+}
+
+extern "C" EncodedJSValue ${typeName}__create(Zig::GlobalObject* globalObject, void* ptr) {
+ auto &vm = globalObject->vm();
+ JSC::Structure* structure = globalObject->${className(typeName)}Structure();
+ ${className(typeName)}* instance = ${className(
+ typeName
+ )}::create(vm, globalObject, structure, ptr);
+ return JSValue::encode(instance);
+}
+
+void ${name}::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, ${prototypeName(
+ typeName
+ )}* prototype)
+{
+
+}
+
+const ClassInfo ${name}::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${name}) };
+
+
+
+
+
+ `;
+}
+
+function renderCachedFieldsHeader(typeName, klass, proto) {
+ const rows = [];
+ for (const name in klass) {
+ if ("cache" in klass[name]) {
+ rows.push(`mutable JSC::WriteBarrier<JSC::Unknown> m_${name};`);
+ }
+ }
+
+ for (const name in proto) {
+ if ("cache" in proto[name]) {
+ rows.push(`mutable JSC::WriteBarrier<JSC::Unknown> m_${name};`);
+ }
+ }
+
+ return rows.join("\n");
+}
+
+function renderDecls(symbolName, typeName, proto) {
+ const rows = [];
+
+ for (const name in proto) {
+ if (
+ "getter" in proto[name] ||
+ ("accessor" in proto[name] && proto[name].getter)
+ ) {
+ rows.push(
+ `extern "C" JSC::EncodedJSValue ${symbolName(
+ typeName,
+ proto[name].getter || proto[name].accessor.getter
+ )}(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject);`,
+ `
+ JSC_DECLARE_CUSTOM_GETTER(${symbolName(typeName, name)}GetterWrap);
+ `.trim(),
+ "\n"
+ );
+ }
+
+ if (
+ "setter" in proto[name] ||
+ ("accessor" in proto[name] && proto[name].setter)
+ ) {
+ rows
+ .push(
+ `extern "C" bool ${symbolName(
+ typeName,
+ proto[name].setter || proto[name].accessor.setter
+ )}(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, EncoedJSValue value);`,
+ `
+ JSC_DECLARE_CUSTOM_SETTER(${symbolName(typeName, name)}SetterWrap);
+ `,
+ "\n"
+ )
+ .trim();
+ }
+
+ if ("fn" in proto[name]) {
+ rows.push(
+ `extern "C" EncodedJSValue ${symbolName(
+ typeName,
+ proto[name].fn
+ )}(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);`,
+
+ `
+ JSC_DECLARE_HOST_FUNCTION(${symbolName(typeName, name)}Callback);
+ `.trim(),
+ "\n"
+ );
+ }
+ }
+
+ return rows.join("\n");
+}
+
+function renderStaticDecls(symbolName, typeName, fields) {
+ const rows = [];
+
+ for (const name in fields) {
+ if (
+ "getter" in fields[name] ||
+ ("accessor" in fields[name] && fields[name].getter)
+ ) {
+ rows.push(
+ `extern "C" JSC_DECLARE_CUSTOM_GETTER(${symbolName(
+ typeName,
+ fields[name].getter || fields[name].accessor.getter
+ )});`
+ );
+ }
+
+ if (
+ "setter" in fields[name] ||
+ ("accessor" in fields[name] && fields[name].setter)
+ ) {
+ rows.push(
+ `extern "C" JSC_DECLARE_CUSTOM_SETTER(${symbolName(
+ typeName,
+ fields[name].setter || fields[name].accessor.setter
+ )});`
+ );
+ }
+
+ if ("fn" in fields[name]) {
+ rows.push(
+ `extern "C" JSC_DECLARE_HOST_FUNCTION(${symbolName(
+ typeName,
+ fields[name].fn
+ )});`
+ );
+ }
+ }
+
+ return rows.join("\n");
+}
+
+function renderFieldsImpl(
+ symbolName: (typeName: string, name: string) => string,
+ typeName: string,
+ obj: ClassDefinition,
+ proto: ClassDefinition["proto"]
+) {
+ const rows: string[] = [];
+
+ if (obj.construct) {
+ rows.push(`
+
+JSC_DEFINE_CUSTOM_GETTER(js${typeName}Constructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
+{
+ VM& vm = JSC::getVM(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto* prototype = jsDynamicCast<${prototypeName(
+ typeName
+ )}*>(JSValue::decode(thisValue));
+
+ if (UNLIKELY(!prototype))
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ return JSValue::encode(globalObject->${className(typeName)}Constructor());
+}
+
+`);
+ }
+
+ for (const name in proto) {
+ if ("cache" in proto[name]) {
+ rows.push(`
+JSC_DEFINE_CUSTOM_GETTER(${symbolName(
+ typeName,
+ name
+ )}GetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ ${className(typeName)}* thisObject = jsCast<${className(
+ typeName
+ )}*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ if (JSValue cachedValue = thisObject->m_${name}.get())
+ return JSValue::encode(cachedValue);
+
+ JSC::JSValue result = JSC::JSValue::decode(
+ ${symbolName(
+ typeName,
+ proto[name].getter
+ )}(thisObject->wrapped(), globalObject)
+ );
+ RETURN_IF_EXCEPTION(throwScope, {});
+ thisObject->m_${name}.set(vm, thisObject, result);
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+}
+`);
+ } else if (
+ "getter" in proto[name] ||
+ ("accessor" in proto[name] && proto[name].getter)
+ ) {
+ rows.push(`
+JSC_DEFINE_CUSTOM_GETTER(${symbolName(
+ typeName,
+ name
+ )}GetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ ${className(typeName)}* thisObject = jsCast<${className(
+ typeName
+ )}*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+ JSC::EncodedJSValue result = ${symbolName(
+ typeName,
+ proto[name].getter
+ )}(thisObject->wrapped(), globalObject);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ RELEASE_AND_RETURN(throwScope, result);
+}
+ `);
+ }
+
+ if (
+ "setter" in proto[name] ||
+ ("accessor" in proto[name] && proto[name].setter)
+ ) {
+ rows.push(
+ `
+JSC_DEFINE_CUSTOM_SETTER(${symbolName(
+ typeName,
+ name
+ )}SetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject>
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ ${className(typeName)}* thisObject = jsCast<${className(
+ typeName
+ )}*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+ auto result = ${symbolName(
+ typeName,
+ roto[name].setter || proto[name].accessor.setter
+ )}(thisObject->wrapped(), lexicalGlobalObject, encodedValue);
+
+ RELEASE_AND_RETURN(throwScope, result);
+}
+`
+ );
+ }
+
+ if ("fn" in proto[name]) {
+ rows.push(`
+JSC_DEFINE_HOST_FUNCTION(${symbolName(
+ typeName,
+ name
+ )}Callback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ ${className(typeName)}* thisObject = jsDynamicCast<${className(
+ typeName
+ )}*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return ${symbolName(
+ typeName,
+ proto[name].fn
+ )}(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+`);
+ }
+ }
+
+ return rows.join("\n");
+}
+
+function generateClassHeader(typeName, obj) {
+ var { klass, proto, JSType = "Object" } = obj;
+ const name = className(typeName);
+
+ const DECLARE_VISIT_CHILDREN = [
+ ...Object.values(klass),
+ ...Object.values(proto),
+ ].find((a) => !!a.cache)
+ ? "DECLARE_VISIT_CHILDREN;"
+ : "";
+
+ return `
+ class ${name} final : public JSC::JSDestructibleObject {
+ public:
+ using Base = JSC::JSDestructibleObject;
+ static ${name}* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx);
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<${name}, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.${clientSubspaceFor(
+ typeName
+ )}.get(); },
+ [](auto& spaces, auto&& space) { spaces.${clientSubspaceFor(
+ typeName
+ )} = WTFMove(space); },
+ [](auto& spaces) { return spaces.${subspaceFor(
+ typeName
+ )}.get(); },
+ [](auto& spaces, auto&& space) { spaces.${subspaceFor(
+ typeName
+ )} = WTFMove(space); });
+ }
+
+ static void destroy(JSC::JSCell*);
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(${JSType}), StructureFlags), info());
+ }
+
+ static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject);
+
+ ~${name}();
+
+ void* wrapped() const { return m_ctx; }
+
+ void detach()
+ {
+ m_ctx = nullptr;
+ }
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(${name}, m_ctx); }
+
+ void* m_ctx { nullptr };
+
+
+ ${name}(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_ctx = sinkPtr;
+ }
+
+ void finishCreation(JSC::VM&);
+
+ ${DECLARE_VISIT_CHILDREN}
+
+ ${renderCachedFieldsHeader(typeName, klass, proto)}
+ };
+ `;
+}
+
+function generateClassImpl(typeName, obj) {
+ const { klass: fields, finalize, proto, construct } = obj;
+ const name = className(typeName);
+ var symbolName = classSymbolName;
+
+ const DEFINE_VISIT_CHILDREN_LIST = [
+ ...Object.entries(fields),
+ ...Object.entries(proto),
+ ]
+ .filter(([name, { cache }]) => !!cache)
+ .map(([name]) => ` visitor.append(thisObject->m_${name});`)
+ .join("\n");
+
+ var DEFINE_VISIT_CHILDREN = "";
+ if (DEFINE_VISIT_CHILDREN_LIST.length) {
+ DEFINE_VISIT_CHILDREN = `
+template<typename Visitor>
+void ${name}::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ ${name}* thisObject = jsCast<${name}*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+${DEFINE_VISIT_CHILDREN_LIST}
+}
+
+DEFINE_VISIT_CHILDREN(${name});
+ `.trim();
+ }
+
+ var output = ``;
+
+ if (finalize) {
+ output += `
+${name}::~${name}()
+{
+ if (m_ctx) {
+ ${classSymbolName(typeName, "finalize")}(m_ctx);
+ }
+}
+`;
+ } else {
+ output += `
+${name}::~${name}()
+{
+}
+`;
+ }
+
+ output += `
+
+
+
+void ${name}::destroy(JSCell* cell)
+{
+ static_cast<${name}*>(cell)->${name}::~${name}();
+}
+
+const ClassInfo ${name}::s_info = { "${typeName}"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${name}) };
+
+void ${name}::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+${name}* ${name}::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) {
+ ${name}* ptr = new (NotNull, JSC::allocateCell<${name}>(vm)) ${name}(vm, structure, ctx);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+
+extern "C" void* ${typeName}__fromJS(JSC::EncodedJSValue value) {
+ ${className(typeName)}* object = JSC::jsDynamicCast<${className(
+ typeName
+ )}*>(JSValue::decode(value));
+ if (!object)
+ return nullptr;
+
+ return object->wrapped();
+}
+
+extern "C" bool ${typeName}__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) {
+ ${className(typeName)}* object = JSC::jsDynamicCast<${className(
+ typeName
+ )}*>(JSValue::decode(value));
+ if (!object)
+ return false;
+
+ object->m_ctx = ptr;
+ return true;
+}
+
+
+extern "C" const size_t ${typeName}__ptrOffset = ${className(
+ typeName
+ )}::offsetOfWrapped();
+
+void ${name}::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<${name}*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+JSObject* ${name}::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
+{
+ return ${prototypeName(typeName)}::create(vm, globalObject, ${prototypeName(
+ typeName
+ )}::createStructure(vm, globalObject, globalObject->objectPrototype()));
+}
+
+${DEFINE_VISIT_CHILDREN}
+
+ `.trim();
+
+ return output;
+}
+
+function generateHeader(typeName, obj) {
+ return (
+ generateClassHeader(typeName, obj).trim() +
+ "\n" +
+ generateConstructorHeader(typeName).trim() +
+ "\n"
+ );
+}
+
+function generateImpl(typeName, obj) {
+ const proto = obj.proto;
+ return [
+ Object.keys(proto).length > 0 && generatePrototype(typeName, obj).trim(),
+ generateConstructorImpl(typeName, obj).trim(),
+ Object.keys(proto).length > 0 && generateClassImpl(typeName, obj).trim(),
+ ]
+ .filter(Boolean)
+ .join("\n\n");
+}
+
+function generateZig(
+ typeName,
+ { klass = {}, proto = {}, construct, finalize } = {} as ClassDefinition
+) {
+ const exports: [string, string][] = [];
+
+ if (construct) {
+ exports.push([`constructor`, classSymbolName(typeName, "construct")]);
+ }
+
+ if (finalize) {
+ exports.push([`finalize`, classSymbolName(typeName, "finalize")]);
+ }
+
+ Object.values(klass).map((a) =>
+ appendSymbols(exports, (name) => classSymbolName(typeName, name), a)
+ );
+ Object.values(proto).map((a) =>
+ appendSymbols(exports, (name) => protoSymbolName(typeName, name), a)
+ );
+
+ function typeCheck() {
+ var output = "";
+
+ if (construct) {
+ output += `
+ if (@TypeOf(${typeName}.constructor) != (fn(*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*${typeName})) {
+ @compileLog("${typeName}.constructor is not a constructor");
+ }
+ `;
+ }
+
+ if (finalize) {
+ output += `
+ if (@TypeOf(${typeName}.finalize) != (fn(*${typeName}) callconv(.C) void)) {
+ @compileLog("${typeName}.finalize is not a finalizer");
+ }
+ `;
+ }
+
+ [...Object.values(proto)].forEach(({ getter, setter, accessor, fn }) => {
+ if (accessor) {
+ getter = accessor.getter;
+ setter = accessor.setter;
+ }
+
+ if (getter) {
+ output += `
+ if (@TypeOf(${typeName}.${getter}) != GetterType)
+ @compileLog(
+ "Expected ${typeName}.${getter} to be a getter"
+ );
+`;
+ }
+
+ if (setter) {
+ output += `
+ if (@TypeOf(${typeName}.${setter}) != SetterType)
+ @compileLog(
+ "Expected ${typeName}.${setter} to be a setter"
+ );`;
+ }
+
+ if (fn) {
+ output += `
+ if (@TypeOf(${typeName}.${fn}) != CallbackType)
+ @compileLog(
+ "Expected ${typeName}.${fn} to be a callback"
+ );`;
+ }
+ });
+
+ [...Object.values(klass)].forEach(({ getter, setter, accessor, fn }) => {
+ if (accessor) {
+ getter = accessor.getter;
+ setter = accessor.setter;
+ }
+
+ if (getter) {
+ output += `
+ if (@TypeOf(${typeName}.${getter}) != StaticGetterType)
+ @compileLog(
+ "Expected ${typeName}.${getter} to be a static getter"
+ );
+`;
+ }
+
+ if (setter) {
+ output += `
+ if (@TypeOf(${typeName}.${setter}) != StaticSetterType)
+ @compileLog(
+ "Expected ${typeName}.${setter} to be a static setter"
+ );`;
+ }
+
+ if (fn) {
+ output += `
+ if (@TypeOf(${typeName}.${fn}) != StaticCallbackType)
+ @compileLog(
+ "Expected ${typeName}.${fn} to be a static callback"
+ );`;
+ }
+ });
+
+ return output;
+ }
+
+ return `
+
+pub const ${className(typeName)} = struct {
+ const ${typeName} = Classes.${typeName};
+ const GetterType = fn(*${typeName}, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const SetterType = fn(*${typeName}, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const CallbackType = fn(*${typeName}, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue;
+
+ /// Return the pointer to the wrapped object.
+ /// If the object does not match the type, return null.
+ pub fn fromJS(value: JSC.JSValue) ?*${typeName} {
+ JSC.markBinding();
+ return ${symbolName(typeName, "fromJS")}(value);
+ }
+
+ /// Create a new instance of ${typeName}
+ pub fn toJS(this: *${typeName}, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding();
+ if (comptime Environment.allow_assert) {
+ const value__ = ${symbolName(
+ typeName,
+ "create"
+ )}(globalObject, this);
+ std.debug.assert(value__.as(${typeName}).? == this); // If this fails, likely a C ABI issue.
+ return value__;
+ } else {
+ return ${symbolName(typeName, "create")}(globalObject, this);
+ }
+ }
+
+ /// Modify the internal ptr to point to a new instance of ${typeName}.
+ pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*${typeName}) bool {
+ JSC.markBinding();
+ return ${symbolName(typeName, "dangerouslySetPtr")}(value, ptr);
+ }
+
+ extern fn ${symbolName(typeName, "fromJS")}(JSC.JSValue) ?*${typeName};
+
+ extern fn ${symbolName(
+ typeName,
+ "create"
+ )}(globalObject: *JSC.JSGlobalObject, ptr: ?*${typeName}) JSC.JSValue;
+
+ extern fn ${typeName}__dangerouslySetPtr(JSC.JSValue, ?*${typeName}) bool;
+
+ comptime {
+ ${typeCheck()}
+ if (!JSC.is_bindgen) {
+${exports
+ .sort(([a], [b]) => a.localeCompare(b))
+ .map(
+ ([internalName, externalName]) =>
+ `@export(${typeName}.${internalName}, .{.name = "${externalName}"});`
+ )
+ .join("\n ")}
+ }
+ }
+};
+
+
+`;
+}
+
+function generateLazyClassStructureHeader(
+ typeName,
+ { klass = {}, proto = {} }
+) {
+ return `
+ JSC::Structure* ${className(
+ typeName
+ )}Structure() { return m_${className(
+ typeName
+ )}.getInitializedOnMainThread(this); }
+ JSC::JSObject* ${className(
+ typeName
+ )}Constructor() { return m_${className(
+ typeName
+ )}.constructorInitializedOnMainThread(this); }
+ JSC::JSValue ${className(typeName)}Prototype() { return m_${className(
+ typeName
+ )}.prototypeInitializedOnMainThread(this); }
+ JSC::LazyClassStructure m_${className(typeName)};
+ `.trim();
+}
+
+function generateLazyClassStructureImpl(typeName, { klass = {}, proto = {} }) {
+ return `
+ m_${className(typeName)}.initLater(
+ [](LazyClassStructure::Initializer& init) {
+ init.setPrototype(WebCore::${className(
+ typeName
+ )}::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
+ init.setStructure(WebCore::${className(
+ typeName
+ )}::createStructure(init.vm, init.global, init.prototype));
+ init.setConstructor(WebCore::${constructorName(
+ typeName
+ )}::create(init.vm, init.global, WebCore::${constructorName(
+ typeName
+ )}::createStructure(init.vm, init.global, init.global->functionPrototype()), jsCast<WebCore::${prototypeName(
+ typeName
+ )}*>(init.prototype)));
+ });
+
+
+ `.trim();
+}
+
+const GENERATED_CLASSES_HEADER = `
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// Generated by src/bun.js/generate-classes.js
+#pragma once
+
+#include "root.h"
+
+namespace Zig {
+}
+
+#include "JSDOMWrapper.h"
+#include <wtf/NeverDestroyed.h>
+
+namespace WebCore {
+using namespace Zig;
+using namespace JSC;
+
+`;
+
+const GENERATED_CLASSES_FOOTER = `
+}
+
+`;
+
+const GENERATED_CLASSES_IMPL_HEADER = `
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// Generated by src/bun.js/generate-classes.js
+#include "root.h"
+
+#include "ZigGlobalObject.h"
+
+#include <JavaScriptCore/DOMJITAbstractHeap.h>
+#include "DOMJITIDLConvert.h"
+#include "DOMJITIDLType.h"
+#include "DOMJITIDLTypeFilter.h"
+#include "DOMJITHelpers.h"
+#include <JavaScriptCore/DFGAbstractHeap.h>
+
+#include "JSDOMConvertBufferSource.h"
+#include "ZigGeneratedClasses.h"
+
+
+
+
+namespace WebCore {
+
+using namespace JSC;
+using namespace Zig;
+
+`;
+
+const GENERATED_CLASSES_IMPL_FOOTER = `
+
+} // namespace WebCore
+
+`;
+
+function initLazyClasses(initLaterFunctions) {
+ return `
+
+void GlobalObject::initGeneratedLazyClasses() {
+ ${initLaterFunctions.map((a) => a.trim()).join("\n ")}
+}
+
+`.trim();
+}
+
+function visitLazyClasses(classes) {
+ return `
+
+template<typename Visitor>
+void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& visitor)
+{
+ ${classes
+ .map((a) => `thisObject->m_${className(a.name)}.visit(visitor);`)
+ .join("\n ")}
+}
+
+ `.trim();
+}
+
+const ZIG_GENERATED_CLASSES_HEADER = `
+const JSC = @import("javascript_core");
+const Classes = @import("./generated_classes_list.zig").Classes;
+const Environment = @import("../../env.zig");
+const std = @import("std");
+
+const StaticGetterType = fn(*JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+const StaticSetterType = fn(*JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+const StaticCallbackType = fn(*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue;
+
+
+
+`;
+
+const directoriesToSearch = [
+ resolve(`${import.meta.dir}/../api`),
+ resolve(`${import.meta.dir}/../webcore`),
+];
+
+function findClasses() {
+ var classes = [];
+ for (let directory of directoriesToSearch) {
+ readdirSync(directory).forEach((file) => {
+ if (file.endsWith(".classes.ts")) {
+ const result = require(`${directory}/${file}`);
+ console.log("Generated", result.default.length, "classes from", file);
+ for (let { name } of result.default) {
+ console.log(` - ${name}`);
+ }
+
+ classes.push(...result.default);
+ }
+ });
+ }
+ return classes;
+}
+
+const classes = findClasses();
+
+await Bun.write(`${import.meta.dir}/../bindings/generated_classes.zig`, [
+ ZIG_GENERATED_CLASSES_HEADER,
+ ...classes.map((a) => generateZig(a.name, a).trim()).join("\n"),
+]);
+await Bun.write(`${import.meta.dir}/../bindings/ZigGeneratedClasses.h`, [
+ GENERATED_CLASSES_HEADER,
+ ...classes.map((a) => generateHeader(a.name, a)),
+ GENERATED_CLASSES_FOOTER,
+]);
+await Bun.write(`${import.meta.dir}/../bindings/ZigGeneratedClasses.cpp`, [
+ GENERATED_CLASSES_IMPL_HEADER,
+ ...classes.map((a) => generateImpl(a.name, a)),
+ GENERATED_CLASSES_IMPL_FOOTER,
+]);
+await Bun.write(
+ `${import.meta.dir}/../bindings/ZigGeneratedClasses+lazyStructureHeader.h`,
+ classes.map((a) => generateLazyClassStructureHeader(a.name, a)).join("\n")
+);
+
+await Bun.write(
+ `${import.meta.dir}/../bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h`,
+ classes.map((a) =>
+ [
+ `std::unique_ptr<GCClient::IsoSubspace> ${clientSubspaceFor(a.name)};`,
+ `std::unique_ptr<GCClient::IsoSubspace> ${clientSubspaceFor(
+ a.name
+ )}Constructor;`,
+ ].join("\n")
+ )
+);
+
+await Bun.write(
+ `${import.meta.dir}/../bindings/ZigGeneratedClasses+DOMIsoSubspaces.h`,
+ classes.map((a) =>
+ [
+ `std::unique_ptr<IsoSubspace> ${subspaceFor(a.name)};`,
+ `std::unique_ptr<IsoSubspace> ${subspaceFor(a.name)}Constructor;`,
+ ].join("\n")
+ )
+);
+
+await Bun.write(
+ `${import.meta.dir}/../bindings/ZigGeneratedClasses+lazyStructureImpl.h`,
+ initLazyClasses(
+ classes.map((a) => generateLazyClassStructureImpl(a.name, a))
+ ) +
+ "\n" +
+ visitLazyClasses(classes)
+);
+
+export {};
diff --git a/src/bun.js/scripts/generate-jssink.js b/src/bun.js/scripts/generate-jssink.js
new file mode 100644
index 000000000..1caea3c22
--- /dev/null
+++ b/src/bun.js/scripts/generate-jssink.js
@@ -0,0 +1,940 @@
+import { resolve } from "path";
+
+const classes = ["ArrayBufferSink", "HTTPResponseSink", "HTTPSResponseSink"];
+const SINK_COUNT = 5;
+
+function names(name) {
+ return {
+ constructor: `JS${name}Constructor`,
+ className: `JS${name}`,
+ controller: `JSReadable${name}Controller`,
+ controllerName: `Readable${name}Controller`,
+ prototypeName: `JS${name}Prototype`,
+ controllerPrototypeName: `JSReadable${name}ControllerPrototype`,
+ };
+}
+function header() {
+ function classTemplate(name) {
+ const { constructor, className, controller } = names(name);
+
+ return `class ${constructor} final : public JSC::InternalFunction {
+ public:
+ using Base = JSC::InternalFunction;
+ static ${constructor}* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype);
+ static constexpr SinkID Sink = SinkID::${name};
+
+ static constexpr unsigned StructureFlags = Base::StructureFlags;
+ static constexpr bool needsDestruction = false;
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<${constructor}, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForJSSinkConstructor.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSinkConstructor = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForJSSinkConstructor.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForJSSinkConstructor = WTFMove(space); });
+ }
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
+ }
+ void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSObject* prototype);
+
+
+ // Must be defined for each specialization class.
+ static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
+
+ private:
+ ${constructor}(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction)
+ : Base(vm, structure, nativeFunction, nativeFunction)
+
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSC::JSObject* prototype);
+ };
+
+ class ${className} final : public JSC::JSDestructibleObject {
+ public:
+ using Base = JSC::JSDestructibleObject;
+ static ${className}* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* sinkPtr);
+ static constexpr SinkID Sink = SinkID::${name};
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<${className}, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForJSSink.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSink = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForJSSink.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForJSSink = WTFMove(space); });
+ }
+
+ static void destroy(JSC::JSCell*);
+ 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());
+ }
+
+ static JSObject* createPrototype(VM& vm, JSDOMGlobalObject& globalObject);
+
+ ~${className}();
+
+ void* wrapped() const { return m_sinkPtr; }
+
+ void detach() {
+ m_sinkPtr = nullptr;
+
+ }
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+
+ void* m_sinkPtr;
+
+ ${className}(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_sinkPtr = sinkPtr;
+ }
+
+ void finishCreation(JSC::VM&);
+ };
+
+ class ${controller} final : public JSC::JSDestructibleObject {
+ public:
+ using Base = JSC::JSDestructibleObject;
+ static ${controller}* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* sinkPtr);
+ static constexpr SinkID Sink = SinkID::${name};
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<${controller}, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForJSSinkController.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSinkController = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForJSSinkController.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForJSSinkController = WTFMove(space); });
+ }
+
+ static void destroy(JSC::JSCell*);
+ 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());
+ }
+ static JSObject* createPrototype(VM& vm, JSDOMGlobalObject& globalObject);
+
+ ~${controller}();
+
+
+ void* wrapped() const { return m_sinkPtr; }
+ void detach();
+
+ void start(JSC::JSGlobalObject *globalObject, JSC::JSValue readableStream, JSC::JSFunction *onPull, JSC::JSFunction *onClose);
+ DECLARE_VISIT_CHILDREN;
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+
+ bool hasPendingActivity() { return m_hasPendingActivity; }
+
+ void* m_sinkPtr;
+ bool m_hasPendingActivity;
+ mutable WriteBarrier<JSC::JSFunction> m_onPull;
+ mutable WriteBarrier<JSC::JSFunction> m_onClose;
+ mutable JSC::Weak<JSObject> m_weakReadableStream;
+ JSC::Weak<${controller}> m_weakThis;
+
+ ${controller}(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_sinkPtr = sinkPtr;
+ m_hasPendingActivity = true;
+ m_weakThis = JSC::Weak<${controller}>(this, getOwner());
+ }
+
+ void finishCreation(JSC::VM&);
+
+ class Owner final : public JSC::WeakHandleOwner {
+ public:
+ bool isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void* context, JSC::AbstractSlotVisitor&, const char**) final
+ {
+ auto* controller = JSC::jsCast<${controller}*>(handle.slot()->asCell());
+ return controller->hasPendingActivity();
+ }
+ void finalize(JSC::Handle<JSC::Unknown>, void* context) final {}
+ };
+
+ static JSC::WeakHandleOwner* getOwner()
+ {
+ static NeverDestroyed<Owner> m_owner;
+ return &m_owner.get();
+ }
+ };
+
+JSC_DECLARE_CUSTOM_GETTER(function${name}__getter);
+
+ `;
+ }
+
+ const outer = `
+// AUTO-GENERATED FILE. DO NOT EDIT.
+// Generated by 'make generate-sink' at ${new Date().toISOString()}
+//
+#pragma once
+
+#include "root.h"
+
+#include "JSDOMWrapper.h"
+#include "wtf/NeverDestroyed.h"
+
+#include "Sink.h"
+
+extern "C" bool JSSink_isSink(JSC::JSGlobalObject*, JSC::EncodedJSValue);
+
+namespace WebCore {
+using namespace JSC;
+
+JSC_DECLARE_HOST_FUNCTION(functionStartDirectStream);
+`;
+
+ const bottom = `
+JSObject* createJSSinkPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WebCore::SinkID sinkID);
+JSObject* createJSSinkControllerPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WebCore::SinkID sinkID);
+Structure* createJSSinkControllerStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WebCore::SinkID sinkID);
+} // namespace WebCore
+`;
+ var templ = outer;
+ for (let name of classes) {
+ templ += classTemplate(name) + "\n";
+ }
+ templ += bottom;
+ return templ;
+}
+
+async function implementation() {
+ const head = `
+// AUTO-GENERATED FILE. DO NOT EDIT.
+// Generated by 'make generate-sink' at ${new Date().toISOString()}
+// To regenerate this file, run:
+//
+// make generate-sink
+//
+#include "root.h"
+#include "JSSink.h"
+
+#include "ActiveDOMObject.h"
+#include "ExtendedDOMClientIsoSubspaces.h"
+#include "ExtendedDOMIsoSubspaces.h"
+#include "IDLTypes.h"
+// #include "JSBlob.h"
+#include "JSDOMAttribute.h"
+#include "JSDOMBinding.h"
+#include "JSDOMConstructor.h"
+#include "JSDOMConvertBase.h"
+#include "JSDOMConvertInterface.h"
+#include "JSDOMConvertStrings.h"
+#include "JSDOMExceptionHandling.h"
+#include "JSDOMGlobalObject.h"
+#include "JSDOMGlobalObjectInlines.h"
+#include "JSDOMOperation.h"
+#include "JSDOMWrapperCache.h"
+#include "ScriptExecutionContext.h"
+#include "WebCoreJSClientData.h"
+#include "JavaScriptCore/FunctionPrototype.h"
+#include "JavaScriptCore/HeapAnalyzer.h"
+
+#include "JavaScriptCore/JSDestructibleObjectHeapCellType.h"
+#include "JavaScriptCore/SlotVisitorMacros.h"
+#include "JavaScriptCore/SubspaceInlines.h"
+#include "wtf/GetPtr.h"
+#include "wtf/PointerPreparations.h"
+#include "wtf/URL.h"
+#include "JavaScriptCore/BuiltinNames.h"
+
+#include "JSBufferEncodingType.h"
+#include "JSBufferPrototypeBuiltins.h"
+#include "JSBufferConstructorBuiltins.h"
+#include "JavaScriptCore/JSBase.h"
+#if ENABLE(MEDIA_SOURCE)
+#include "BufferMediaSource.h"
+#include "JSMediaSource.h"
+#endif
+
+// #include "JavaScriptCore/JSTypedArrayViewPrototype.h"
+#include "JavaScriptCore/JSArrayBufferViewInlines.h"
+
+#include "JSReadableStream.h"
+#include "BunClientData.h"
+#include "JavaScriptCore/Weak.h"
+#include "JavaScriptCore/WeakInlines.h"
+
+
+
+namespace WebCore {
+using namespace JSC;
+
+
+
+
+
+JSC_DEFINE_HOST_FUNCTION(functionStartDirectStream, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame *callFrame))
+{
+
+ auto& vm = lexicalGlobalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+
+ JSC::JSValue readableStream = callFrame->argument(0);
+ JSC::JSValue onPull = callFrame->argument(1);
+ JSC::JSValue onClose = callFrame->argument(2);
+ if (!readableStream.isObject()) {
+ scope.throwException(globalObject, JSC::createTypeError(globalObject, "Expected ReadableStream"_s));
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ if (!onPull.isObject() || !onPull.isCallable()) {
+ onPull = JSC::jsUndefined();
+ }
+
+ if (!onClose.isObject() || !onClose.isCallable()) {
+ onClose = JSC::jsUndefined();
+ }
+
+ JSC::JSFunction *onPullFunction = JSC::jsDynamicCast<JSC::JSFunction*>(onPull);
+ JSC::JSFunction *onCloseFunction = JSC::jsDynamicCast<JSC::JSFunction*>(onClose);
+
+`;
+ var templ = head;
+
+ var isFirst = true;
+ for (let name of classes) {
+ const {
+ className,
+ controller,
+ prototypeName,
+ controllerPrototypeName,
+ constructor,
+ } = names(name);
+
+ templ += `
+
+ ${
+ isFirst ? "" : "else"
+ } if (WebCore::${controller}* ${name}Controller = JSC::jsDynamicCast<WebCore::${controller}*>(callFrame->thisValue())) {
+ if (${name}Controller->wrapped() == nullptr) {
+ scope.throwException(globalObject, JSC::createTypeError(globalObject, "Cannot start stream with closed controller"_s));
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ ${name}Controller->start(globalObject, readableStream, onPullFunction, onCloseFunction);
+ }
+`;
+ isFirst = false;
+ }
+
+ templ += `
+ else {
+ scope.throwException(globalObject, JSC::createTypeError(globalObject, "Unknown direct controller. This is a bug in Bun."_s));
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsUndefined()));
+}
+`;
+
+ for (let name of classes) {
+ const {
+ className,
+ controller,
+ prototypeName,
+ controllerName,
+ controllerPrototypeName,
+ constructor,
+ } = names(name);
+ const protopad = `${controller}__close`.length;
+ const padding = `${name}__doClose`.length;
+ templ += `
+JSC_DEFINE_CUSTOM_GETTER(function${name}__getter, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+
+ return JSC::JSValue::encode(globalObject->${name}());
+}
+
+
+JSC_DECLARE_HOST_FUNCTION(${controller}__close);
+JSC_DEFINE_HOST_FUNCTION(${controller}__close, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame *callFrame))
+{
+
+ auto& vm = lexicalGlobalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ WebCore::${controller}* controller = JSC::jsDynamicCast<WebCore::${controller}*>(callFrame->thisValue());
+ if (!controller) {
+ scope.throwException(globalObject, JSC::createTypeError(globalObject, "Expected ${controller}"_s));
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ void *ptr = controller->wrapped();
+ if (ptr == nullptr) {
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ controller->detach();
+ ${name}__close(lexicalGlobalObject, ptr);
+ // Release the controller right before close.
+ controller->m_hasPendingActivity = false;
+ return JSC::JSValue::encode(JSC::jsUndefined());
+}
+
+JSC_DECLARE_HOST_FUNCTION(${controller}__end);
+JSC_DEFINE_HOST_FUNCTION(${controller}__end, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame *callFrame))
+{
+
+ auto& vm = lexicalGlobalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ WebCore::${controller}* controller = JSC::jsDynamicCast<WebCore::${controller}*>(callFrame->thisValue());
+ if (!controller) {
+ scope.throwException(globalObject, JSC::createTypeError(globalObject, "Expected ${controller}"_s));
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ void *ptr = controller->wrapped();
+ if (ptr == nullptr) {
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ controller->detach();
+ return ${name}__endWithSink(ptr, lexicalGlobalObject);
+}
+
+
+JSC_DECLARE_HOST_FUNCTION(${name}__doClose);
+JSC_DEFINE_HOST_FUNCTION(${name}__doClose, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame *callFrame))
+{
+
+ auto& vm = lexicalGlobalObject->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ WebCore::${className}* sink = JSC::jsDynamicCast<WebCore::${className}*>(callFrame->thisValue());
+ if (!sink) {
+ scope.throwException(globalObject, JSC::createTypeError(globalObject, "Expected ${name}"_s));
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ void *ptr = sink->wrapped();
+ if (ptr == nullptr) {
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ sink->detach();
+ ${name}__close(lexicalGlobalObject, ptr);
+ return JSC::JSValue::encode(JSC::jsUndefined());
+}
+
+
+`;
+ }
+
+ templ += `
+#include "JSSinkLookupTable.h"
+ `;
+
+ for (let name of classes) {
+ const {
+ className,
+ controller,
+ prototypeName,
+ controllerName,
+ controllerPrototypeName,
+ constructor,
+ } = names(name);
+ const protopad = `${controller}__close`.length;
+ const padding = `${name}__doClose`.length;
+ templ += `
+/* Source for JS${name}PrototypeTableValues.lut.h
+@begin JS${name}PrototypeTable
+ close ${`${name}__doClose`.padEnd(
+ padding + 8
+ )} ReadOnly|DontDelete|Function 0
+ flush ${`${name}__flush`.padEnd(
+ padding + 8
+ )} ReadOnly|DontDelete|Function 1
+ end ${`${name}__end`.padEnd(
+ padding + 8
+ )} ReadOnly|DontDelete|Function 0
+ start ${`${name}__start`.padEnd(
+ padding + 8
+ )} ReadOnly|DontDelete|Function 1
+ write ${`${name}__write`.padEnd(
+ padding + 8
+ )} ReadOnly|DontDelete|Function 1
+@end
+*/
+
+
+/* Source for ${controllerPrototypeName}TableValues.lut.h
+@begin ${controllerPrototypeName}Table
+ close ${`${controller}__close`.padEnd(
+ protopad + 4
+ )} ReadOnly|DontDelete|Function 0
+ flush ${`${name}__flush`.padEnd(
+ protopad + 4
+ )} ReadOnly|DontDelete|Function 1
+ end ${`${controller}__end`.padEnd(
+ protopad + 4
+ )} ReadOnly|DontDelete|Function 0
+ start ${`${name}__start`.padEnd(
+ protopad + 4
+ )} ReadOnly|DontDelete|Function 1
+ write ${`${name}__write`.padEnd(
+ protopad + 4
+ )} ReadOnly|DontDelete|Function 1
+@end
+*/
+
+ `;
+ }
+
+ const footer = `
+} // namespace WebCore
+
+`;
+
+ for (let name of classes) {
+ const {
+ className,
+ controller,
+ prototypeName,
+ controllerPrototypeName,
+ constructor,
+ controllerName,
+ } = names(name);
+ templ += `
+#pragma mark - ${name}
+
+class ${prototypeName} final : public JSC::JSNonFinalObject {
+public:
+ using Base = JSC::JSNonFinalObject;
+
+ static ${prototypeName}* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ ${prototypeName}* ptr = new (NotNull, JSC::allocateCell<${prototypeName}>(vm)) ${prototypeName}(vm, globalObject, structure);
+ ptr->finishCreation(vm, globalObject);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ 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:
+ ${prototypeName}(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+};
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(${prototypeName}, ${prototypeName}::Base);
+
+class ${controllerPrototypeName} final : public JSC::JSNonFinalObject {
+ public:
+ using Base = JSC::JSNonFinalObject;
+
+ static ${controllerPrototypeName}* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ ${controllerPrototypeName}* ptr = new (NotNull, JSC::allocateCell<${controllerPrototypeName}>(vm)) ${controllerPrototypeName}(vm, globalObject, structure);
+ ptr->finishCreation(vm, globalObject);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ 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:
+ ${controllerPrototypeName}(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+ };
+ STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(${controllerPrototypeName}, ${controllerPrototypeName}::Base);
+
+const ClassInfo ${prototypeName}::s_info = { "${name}"_s, &Base::s_info, &JS${name}PrototypeTable, nullptr, CREATE_METHOD_TABLE(${prototypeName}) };
+const ClassInfo ${className}::s_info = { "${name}"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${className}) };
+const ClassInfo ${constructor}::s_info = { "${name}"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${constructor}) };
+
+
+const ClassInfo ${controllerPrototypeName}::s_info = { "${controllerName}"_s, &Base::s_info, &${controllerPrototypeName}Table, nullptr, CREATE_METHOD_TABLE(${controllerPrototypeName}) };
+const ClassInfo ${controller}::s_info = { "${controllerName}"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(${controller}) };
+
+${className}::~${className}()
+{
+ if (m_sinkPtr) {
+ ${name}__finalize(m_sinkPtr);
+ }
+}
+
+
+${controller}::~${controller}()
+{
+ if (m_sinkPtr) {
+ ${name}__finalize(m_sinkPtr);
+ }
+}
+
+JSObject* ${className}::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+ return ${prototypeName}::create(vm, &globalObject, ${prototypeName}::createStructure(vm, &globalObject, globalObject.objectPrototype()));
+}
+
+JSObject* JS${controllerName}::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
+{
+ return ${controllerPrototypeName}::create(vm, &globalObject, ${controllerPrototypeName}::createStructure(vm, &globalObject, globalObject.objectPrototype()));
+}
+
+void JS${controllerName}::detach() {
+ m_sinkPtr = nullptr;
+ m_onPull.clear();
+
+ auto readableStream = m_weakReadableStream.get();
+ auto onClose = m_onClose.get();
+ m_onClose.clear();
+
+ if (readableStream && onClose) {
+ JSC::JSGlobalObject *globalObject = this->globalObject();
+ auto callData = JSC::getCallData(onClose);
+ JSC::MarkedArgumentBuffer arguments;
+ arguments.append(readableStream);
+ arguments.append(jsUndefined());
+ JSC::call(globalObject, onClose, callData, JSC::jsUndefined(), arguments);
+ }
+
+ m_weakReadableStream.clear();
+}
+`;
+
+ templ += `
+
+${constructor}* ${constructor}::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSObject* prototype)
+{
+ ${constructor}* ptr = new (NotNull, JSC::allocateCell<${constructor}>(vm)) ${constructor}(vm, structure, ${name}__construct);
+ ptr->finishCreation(vm, globalObject, prototype);
+ return ptr;
+}
+
+${className}* ${className}::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* sinkPtr)
+{
+ ${className}* ptr = new (NotNull, JSC::allocateCell<${className}>(vm)) ${className}(vm, structure, sinkPtr);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+${controller}* ${controller}::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* sinkPtr)
+{
+ ${controller}* ptr = new (NotNull, JSC::allocateCell<${controller}>(vm)) ${controller}(vm, structure, sinkPtr);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+void ${constructor}::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSObject* prototype)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+ initializeProperties(vm, globalObject, prototype);
+}
+
+JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES ${constructor}::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) {
+ return ${name}__construct(globalObject, callFrame);
+}
+
+
+void ${constructor}::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSObject* prototype)
+{
+ putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+ JSString* nameString = jsNontrivialString(vm, "${name}"_s);
+ m_originalName.set(vm, this, nameString);
+ putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+}
+
+void ${prototypeName}::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+ reifyStaticProperties(vm, ${className}::info(), ${className}PrototypeTableValues, *this);
+ putDirect(vm, JSC::Identifier::fromString(vm, "sinkId"_s), JSC::jsNumber(${className}::Sink), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+void ${controllerPrototypeName}::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+ reifyStaticProperties(vm, ${controller}::info(), ${controller}PrototypeTableValues, *this);
+ putDirect(vm, JSC::Identifier::fromString(vm, "sinkId"_s), JSC::jsNumber(${className}::Sink), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+void ${className}::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+void ${controller}::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+
+void ${className}::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<${className}*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ analyzer.setWrappedObjectForCell(cell, wrapped);
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+void ${controller}::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<${controller}*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ analyzer.setWrappedObjectForCell(cell, wrapped);
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+
+template<typename Visitor>
+void ${controller}::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ ${controller}* thisObject = jsCast<${controller}*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->m_onPull);
+ visitor.append(thisObject->m_onClose);
+ visitor.append(thisObject->m_weakReadableStream);
+}
+
+DEFINE_VISIT_CHILDREN(${controller});
+
+
+void ${controller}::start(JSC::JSGlobalObject *globalObject, JSC::JSValue readableStream, JSC::JSFunction *onPull, JSC::JSFunction *onClose) {
+ this->m_weakReadableStream = JSC::Weak<JSC::JSObject>(readableStream.getObject());
+ this->m_onPull.set(globalObject->vm(), this, onPull);
+ this->m_onClose.set(globalObject->vm(), this, onClose);
+}
+
+void ${className}::destroy(JSCell* cell)
+{
+ static_cast<${className}*>(cell)->${className}::~${className}();
+}
+
+
+void ${controller}::destroy(JSCell* cell)
+{
+ static_cast<${controller}*>(cell)->${controller}::~${controller}();
+}
+
+
+`;
+ }
+
+ templ += `
+ JSObject* createJSSinkPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, SinkID sinkID)
+ {
+ switch (sinkID) {
+ `;
+ for (let name of classes) {
+ templ += `
+ case ${name}:
+ return JS${name}Prototype::create(vm, globalObject, JS${name}Prototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
+`;
+ }
+ templ += `
+default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+}`;
+
+ templ += `
+JSObject* createJSSinkControllerPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, SinkID sinkID)
+{
+ switch (sinkID) {
+ `;
+ for (let name of classes) {
+ const { controllerPrototypeName } = names(name);
+ templ += `
+ case ${name}:
+ return ${controllerPrototypeName}::create(vm, globalObject, ${controllerPrototypeName}::createStructure(vm, globalObject, globalObject->objectPrototype()));
+`;
+ }
+ templ += `
+default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+}`;
+
+ templ += `
+Structure* createJSSinkControllerStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, SinkID sinkID)
+{
+ switch (sinkID) {
+ `;
+ for (let name of classes) {
+ templ += `
+ case ${name}: {
+ auto* prototype = createJSSinkControllerPrototype(vm, globalObject, sinkID);
+ return JSReadable${name}Controller::createStructure(vm, globalObject, prototype);
+ }
+`;
+ }
+ templ += `
+default:
+ RELEASE_ASSERT_NOT_REACHED();
+ }
+}`;
+
+ templ += footer;
+
+ for (let name of classes) {
+ const {
+ className,
+ controller,
+ prototypeName,
+ controllerPrototypeName,
+ constructor,
+ } = names(name);
+
+ templ += `
+extern "C" JSC__JSValue ${name}__createObject(JSC__JSGlobalObject* arg0, void* sinkPtr)
+{
+ auto& vm = arg0->vm();
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg0);
+ JSC::JSValue prototype = globalObject->${name}Prototype();
+ JSC::Structure* structure = WebCore::JS${name}::createStructure(vm, globalObject, prototype);
+ return JSC::JSValue::encode(WebCore::JS${name}::create(vm, globalObject, structure, sinkPtr));
+}
+
+extern "C" void* ${name}__fromJS(JSC__JSGlobalObject* arg0, JSC__JSValue JSValue1)
+{
+ JSC::VM& vm = WebCore::getVM(arg0);
+ if (auto* sink = JSC::jsDynamicCast<WebCore::JS${name}*>(JSC::JSValue::decode(JSValue1)))
+ return sink->wrapped();
+
+ if (auto* controller = JSC::jsDynamicCast<WebCore::${controller}*>(JSC::JSValue::decode(JSValue1)))
+ return controller->wrapped();
+
+ return nullptr;
+}
+
+extern "C" void ${name}__detachPtr(JSC__JSValue JSValue0)
+{
+ if (auto* sink = JSC::jsDynamicCast<WebCore::JS${name}*>(JSC::JSValue::decode(JSValue0))) {
+ sink->detach();
+ return;
+ }
+
+
+ if (auto* controller = JSC::jsDynamicCast<WebCore::${controller}*>(JSC::JSValue::decode(JSValue0))) {
+ controller->detach();
+ return;
+ }
+}
+
+extern "C" JSC__JSValue ${name}__assignToStream(JSC__JSGlobalObject* arg0, JSC__JSValue stream, void* sinkPtr, void **controllerValue)
+{
+ auto& vm = arg0->vm();
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(arg0);
+
+ JSC::Structure* structure = WebCore::getDOMStructure<WebCore::${controller}>(vm, *globalObject);
+ WebCore::${controller} *controller = WebCore::${controller}::create(vm, globalObject, structure, sinkPtr);
+ *controllerValue = reinterpret_cast<void*>(JSC::JSValue::encode(controller));
+ return globalObject->assignToStream(JSC::JSValue::decode(stream), controller);
+}
+
+extern "C" void ${name}__onReady(JSC__JSValue controllerValue, JSC__JSValue amt, JSC__JSValue offset)
+{
+ WebCore::${controller}* controller = JSC::jsCast<WebCore::${controller}*>(JSC::JSValue::decode(controllerValue).getObject());
+
+ JSC::JSFunction *function = controller->m_onPull.get();
+ if (function == nullptr)
+ return;
+ JSC::JSGlobalObject *globalObject = controller->globalObject();
+
+ auto callData = JSC::getCallData(function);
+ JSC::MarkedArgumentBuffer arguments;
+ arguments.append(controller);
+ arguments.append(JSC::JSValue::decode(amt));
+ arguments.append(JSC::JSValue::decode(offset));
+
+ JSC::call(globalObject, function, callData, JSC::jsUndefined(), arguments);
+}
+
+extern "C" void ${name}__onStart(JSC__JSValue controllerValue)
+{
+
+}
+
+extern "C" void ${name}__onClose(JSC__JSValue controllerValue, JSC__JSValue reason)
+{
+ WebCore::${controller}* controller = JSC::jsCast<WebCore::${controller}*>(JSC::JSValue::decode(controllerValue).getObject());
+
+ JSC::JSFunction *function = controller->m_onClose.get();
+ if (function == nullptr)
+ return;
+ // only call close once
+ controller->m_onClose.clear();
+ JSC::JSGlobalObject *globalObject = controller->globalObject();
+
+ auto callData = JSC::getCallData(function);
+ JSC::MarkedArgumentBuffer arguments;
+ auto readableStream = controller->m_weakReadableStream.get();
+ arguments.append(readableStream ? readableStream : JSC::jsUndefined());
+
+ arguments.append(JSC::JSValue::decode(reason));
+ JSC::call(globalObject, function, callData, JSC::jsUndefined(), arguments);
+}
+
+`;
+ }
+ return templ;
+}
+
+await Bun.write(resolve(import.meta.dir + "/../bindings/JSSink.h"), header());
+await Bun.write(
+ resolve(import.meta.dir + "/../bindings/JSSink.cpp"),
+ await implementation()
+);