Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2401,9 +2401,9 @@ declare module "bun" {
*
* @param begin - start offset in bytes
* @param end - absolute offset in bytes (relative to 0)
* @param contentType - MIME type for the new BunFile
* @param contentType - MIME type for the new Blob
*/
slice(begin?: number, end?: number, contentType?: string): BunFile;
slice(begin?: number, end?: number, contentType?: string): Blob;

/**
* Offset any operation on the file starting at `begin`
Expand All @@ -2413,16 +2413,16 @@ declare module "bun" {
* If `begin` > 0, {@link Bun.write}() is slower on macOS
*
* @param begin - start offset in bytes
* @param contentType - MIME type for the new BunFile
* @param contentType - MIME type for the new Blob
*/
slice(begin?: number, contentType?: string): BunFile;
slice(begin?: number, contentType?: string): Blob;

/**
* Slice the file from the beginning to the end, optionally with a new MIME type.
*
* @param contentType - MIME type for the new BunFile
* @param contentType - MIME type for the new Blob
*/
slice(contentType?: string): BunFile;
slice(contentType?: string): Blob;

/**
* Incremental writer for files and pipes.
Expand Down
5 changes: 3 additions & 2 deletions src/jsc/bindings/DOMFormData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ void DOMFormData::append(const String& name, const String& value)

void DOMFormData::append(const String& name, RefPtr<Blob> blob, const String& filename)
{
blob->setFileName(replaceUnpairedSurrogatesWithReplacementCharacter(String(filename)));
// A non-null fileName is what makes toJS(Blob&) wrap this entry as a File.
blob->setFileName(filename.isNull() ? emptyString() : replaceUnpairedSurrogatesWithReplacementCharacter(String(filename)));
Comment thread
robobun marked this conversation as resolved.
m_items.append({ replaceUnpairedSurrogatesWithReplacementCharacter(String(name)), blob });
}
void DOMFormData::remove(const StringView name)
Expand Down Expand Up @@ -148,7 +149,7 @@ void DOMFormData::set(const String& name, const String& value)

void DOMFormData::set(const String& name, RefPtr<Blob> blob, const String& filename)
{
blob->setFileName(filename);
blob->setFileName(filename.isNull() ? emptyString() : filename);
set(name, { name, blob });
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

Expand Down
182 changes: 145 additions & 37 deletions src/jsc/bindings/JSDOMFile.cpp
Original file line number Diff line number Diff line change
@@ -1,66 +1,149 @@
#include "root.h"
#include "ZigGeneratedClasses.h"
#include "ZigGlobalObject.h"
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/InternalFunction.h>
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/LazyClassStructure.h>
#include <JavaScriptCore/LazyClassStructureInlines.h>
#include "JSDOMFile.h"

using namespace JSC;

extern "C" SYSV_ABI void* JSDOMFile__construct(JSC::JSGlobalObject*, JSC::CallFrame* callframe);
extern "C" SYSV_ABI bool JSDOMFile__hasInstance(EncodedJSValue, JSC::JSGlobalObject*, EncodedJSValue);

// TODO: make this inehrit from JSBlob instead of InternalFunction
// That will let us remove this hack for [Symbol.hasInstance] and fix the prototype chain.
class JSDOMFile : public JSC::InternalFunction {
using Base = JSC::InternalFunction;
extern "C" SYSV_ABI JSC::EncodedJSValue JSDOMFile__getName(void* ptr, JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject);
extern "C" SYSV_ABI bool JSDOMFile__setName(void* ptr, JSC::EncodedJSValue thisValue, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value);
extern "C" SYSV_ABI JSC::EncodedJSValue JSDOMFile__getLastModified(void* ptr, JSC::JSGlobalObject* globalObject);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

namespace Bun {

JSC_DEFINE_CUSTOM_GETTER(domFilePrototype_nameGetter, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue encodedThisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = dynamicDowncast<WebCore::JSBlob>(JSValue::decode(encodedThisValue));
if (!thisObject) [[unlikely]] {
return JSValue::encode(jsUndefined());
}
JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);

if (JSValue cachedValue = thisObject->m_name.get())
return JSValue::encode(cachedValue);

JSC::JSValue result = JSC::JSValue::decode(
JSDOMFile__getName(thisObject->wrapped(), encodedThisValue, lexicalGlobalObject));
RETURN_IF_EXCEPTION(throwScope, {});
thisObject->m_name.set(vm, thisObject, result);
RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
}

JSC_DEFINE_CUSTOM_SETTER(domFilePrototype_nameSetter, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue encodedThisValue, EncodedJSValue encodedValue, PropertyName attributeName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = dynamicDowncast<WebCore::JSBlob>(JSValue::decode(encodedThisValue));
if (!thisObject) [[unlikely]] {
return false;
}
JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
bool result = JSDOMFile__setName(thisObject->wrapped(), encodedThisValue, lexicalGlobalObject, encodedValue);
RELEASE_AND_RETURN(throwScope, result);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

JSC_DEFINE_CUSTOM_GETTER(domFilePrototype_lastModifiedGetter, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue encodedThisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = dynamicDowncast<WebCore::JSBlob>(JSValue::decode(encodedThisValue));
if (!thisObject) [[unlikely]] {
return JSValue::encode(jsUndefined());
}
JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
JSC::EncodedJSValue result = JSDOMFile__getLastModified(thisObject->wrapped(), lexicalGlobalObject);
RETURN_IF_EXCEPTION(throwScope, {});
RELEASE_AND_RETURN(throwScope, result);
}

static const HashTableValue JSDOMFilePrototypeTableValues[] = {
{ "name"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor | PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, domFilePrototype_nameGetter, domFilePrototype_nameSetter } },
{ "lastModified"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor | PropertyAttribute::DOMAttribute | PropertyAttribute::DontDelete), NoIntrinsic, { HashTableValue::GetterSetterType, domFilePrototype_lastModifiedGetter, 0 } },
};
Comment thread
claude[bot] marked this conversation as resolved.

class JSDOMFilePrototype final : public JSC::JSNonFinalObject {
public:
JSDOMFile(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, call, construct)
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;

static JSDOMFilePrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSDOMFilePrototype* prototype = new (NotNull, JSC::allocateCell<JSDOMFilePrototype>(vm)) JSDOMFilePrototype(vm, structure);
prototype->finishCreation(vm, globalObject);
return prototype;
}

DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
auto* structure = JSC::Structure::create(vm, globalObject, prototype, TypeInfo(JSC::ObjectType, StructureFlags), info());
structure->setMayBePrototype(true);
return structure;
}

static constexpr unsigned StructureFlags = (Base::StructureFlags & ~ImplementsDefaultHasInstance) | ImplementsHasInstance;
DECLARE_INFO;

template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.internalFunctionSpace();
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDOMFilePrototype, Base);
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)

private:
JSDOMFilePrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(InternalFunctionType, StructureFlags), info());
}

void finishCreation(JSC::VM& vm)
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
Base::finishCreation(vm, 2, "File"_s);
Base::finishCreation(vm);
reifyStaticProperties(vm, WebCore::JSBlob::info(), JSDOMFilePrototypeTableValues, *this);
Comment thread
robobun marked this conversation as resolved.
this->putDirect(vm, vm.propertyNames->toStringTagSymbol, jsString(vm, String("File"_s)), PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly | 0);
}
};

static JSDOMFile* create(JSC::VM& vm, JSGlobalObject* globalObject)
{
auto* zigGlobal = defaultGlobalObject(globalObject);
auto structure = createStructure(vm, globalObject, zigGlobal->functionPrototype());
auto* object = new (NotNull, JSC::allocateCell<JSDOMFile>(vm)) JSDOMFile(vm, structure);
object->finishCreation(vm);
const JSC::ClassInfo JSDOMFilePrototype::s_info = { "File"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFilePrototype) };

// This is not quite right. But we'll fix it if someone files an issue about it.
object->putDirect(vm, vm.propertyNames->prototype, zigGlobal->JSBlobPrototype(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | 0);
class JSDOMFileConstructor final : public JSC::InternalFunction {
using Base = JSC::InternalFunction;

return object;
public:
JSDOMFileConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, call, construct)
{
}

static bool customHasInstance(JSObject* object, JSGlobalObject* globalObject, JSValue value)
DECLARE_INFO;

static constexpr unsigned StructureFlags = Base::StructureFlags;

template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if (!value.isObject())
return false;
return &vm.internalFunctionSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(InternalFunctionType, StructureFlags), info());
}

// Note: this breaks [Symbol.hasInstance]
// We must do this for now until we update the code generator to export classes
return JSDOMFile__hasInstance(JSValue::encode(object), globalObject, JSValue::encode(value));
static JSDOMFileConstructor* create(JSC::VM& vm, JSGlobalObject* globalObject, JSObject* prototype)
{
auto* zigGlobal = defaultGlobalObject(globalObject);
auto* structure = createStructure(vm, globalObject, zigGlobal->JSBlobConstructor());
auto* object = new (NotNull, JSC::allocateCell<JSDOMFileConstructor>(vm)) JSDOMFileConstructor(vm, structure);
object->finishCreation(vm, prototype);
return object;
}

static JSC_HOST_CALL_ATTRIBUTES JSC::EncodedJSValue construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
Expand All @@ -69,15 +152,15 @@ class JSDOMFile : public JSC::InternalFunction {
auto& vm = JSC::getVM(globalObject);
JSObject* newTarget = asObject(callFrame->newTarget());
auto* constructor = globalObject->JSDOMFileConstructor();
Structure* structure = globalObject->JSBlobStructure();
Structure* structure = globalObject->JSDOMFileStructure();
if (constructor != newTarget) {
auto scope = DECLARE_THROW_SCOPE(vm);

auto* functionGlobalObject = static_cast<Zig::GlobalObject*>(
auto* functionGlobalObject = defaultGlobalObject(
// ShadowRealm functions belong to a different global object.
getFunctionRealm(lexicalGlobalObject, newTarget));
RETURN_IF_EXCEPTION(scope, {});
structure = InternalFunction::createSubclassStructure(lexicalGlobalObject, newTarget, functionGlobalObject->JSBlobStructure());
structure = InternalFunction::createSubclassStructure(lexicalGlobalObject, newTarget, functionGlobalObject->JSDOMFileStructure());
RETURN_IF_EXCEPTION(scope, {});
}

Expand All @@ -97,15 +180,40 @@ class JSDOMFile : public JSC::InternalFunction {
throwTypeError(lexicalGlobalObject, scope, "Class constructor File cannot be invoked without 'new'"_s);
return {};
}
};

const JSC::ClassInfo JSDOMFile::s_info = { "File"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFile) };
private:
void finishCreation(JSC::VM& vm, JSObject* prototype)
{
Base::finishCreation(vm, 2, "File"_s);
putDirect(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0);
}
};

namespace Bun {
const JSC::ClassInfo JSDOMFileConstructor::s_info = { "File"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFileConstructor) };

JSC::JSObject* createJSDOMFileConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
void initJSDOMFileClassStructure(JSC::LazyClassStructure::Initializer& init)
{
return JSDOMFile::create(vm, globalObject);
auto* zigGlobal = defaultGlobalObject(init.global);
auto* superPrototype = zigGlobal->JSBlobPrototype();
auto* protoStructure = JSDOMFilePrototype::createStructure(init.vm, init.global, superPrototype);
auto* prototype = JSDOMFilePrototype::create(init.vm, init.global, protoStructure);
auto* structure = WebCore::JSBlob::createStructure(init.vm, init.global, prototype);
auto* constructor = JSDOMFileConstructor::create(init.vm, init.global, prototype);
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
}

extern "C" SYSV_ABI size_t Blob__estimatedSize(void* ptr);

extern "C" SYSV_ABI JSC::EncodedJSValue BUN__createJSDOMFileUnsafely(JSC::JSGlobalObject* lexicalGlobalObject, void* ptr)
{
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto& vm = JSC::getVM(globalObject);
auto* structure = globalObject->JSDOMFileStructure();
auto* instance = WebCore::JSBlob::create(vm, globalObject, structure, ptr);
vm.heap.reportExtraMemoryAllocated(instance, Blob__estimatedSize(ptr));
return JSValue::encode(instance);
}
Comment thread
robobun marked this conversation as resolved.

} // namespace Bun
3 changes: 2 additions & 1 deletion src/jsc/bindings/JSDOMFile.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#pragma once

#include "root.h"
#include <JavaScriptCore/LazyClassStructure.h>

namespace Bun {
JSC::JSObject* createJSDOMFileConstructor(JSC::VM&, JSC::JSGlobalObject*);
void initJSDOMFileClassStructure(JSC::LazyClassStructure::Initializer&);
}
2 changes: 1 addition & 1 deletion src/jsc/bindings/JSS3File.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ JSC::Structure* JSS3File::createStructure(JSC::JSGlobalObject* globalObject)
{
auto& vm = JSC::getVM(globalObject);

JSC::JSObject* superPrototype = defaultGlobalObject(globalObject)->JSBlobPrototype();
JSC::JSObject* superPrototype = defaultGlobalObject(globalObject)->JSDOMFileStructure()->storedPrototypeObject();
auto* protoStructure = JSS3FilePrototype::createStructure(vm, globalObject, superPrototype);
auto* prototype = JSS3FilePrototype::create(vm, globalObject, protoStructure);
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info(), NonArray);
Expand Down
11 changes: 5 additions & 6 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2136,12 +2136,6 @@ void GlobalObject::finishCreation(VM& vm)
init.set(CustomGetterSetter::create(init.vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter));
});

m_JSDOMFileConstructor.initLater(
[](const Initializer<JSObject>& init) {
JSObject* fileConstructor = Bun::createJSDOMFileConstructor(init.vm, init.owner);
init.set(fileConstructor);
});

m_cryptoObject.initLater(
[](const Initializer<JSObject>& init) {
JSC::JSGlobalObject* globalObject = init.owner;
Expand Down Expand Up @@ -2184,6 +2178,11 @@ void GlobalObject::finishCreation(VM& vm)
init.set(Bun::createJSS3FileStructure(init.vm, init.owner));
});

m_JSDOMFileClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
Bun::initJSDOMFileClassStructure(init);
});

m_S3ErrorStructure.initLater(
[](const Initializer<Structure>& init) {
init.set(Bun::createS3ErrorStructure(init.vm, init.owner));
Expand Down
5 changes: 3 additions & 2 deletions src/jsc/bindings/ZigGlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ class GlobalObject : public Bun::GlobalScope {
V(public, LazyPropertyOfGlobalObject<Structure>, m_JSS3FileStructure) \
V(public, LazyPropertyOfGlobalObject<Structure>, m_S3ErrorStructure) \
\
V(public, JSC::LazyClassStructure, m_JSDOMFileClassStructure) \
V(public, JSC::LazyClassStructure, m_JSStatsClassStructure) \
V(public, JSC::LazyClassStructure, m_JSStatsBigIntClassStructure) \
V(public, JSC::LazyClassStructure, m_JSStatFSClassStructure) \
Expand Down Expand Up @@ -674,7 +675,6 @@ class GlobalObject : public Bun::GlobalScope {
V(private, LazyPropertyOfGlobalObject<Structure>, m_importMetaObjectStructure) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_importMetaBakeObjectStructure) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_asyncBoundFunctionStructure) \
V(public, LazyPropertyOfGlobalObject<JSC::JSObject>, m_JSDOMFileConstructor) \
V(public, LazyPropertyOfGlobalObject<JSC::JSObject>, m_JSMIMEParamsConstructor) \
V(public, LazyPropertyOfGlobalObject<JSC::JSObject>, m_JSMIMETypeConstructor) \
\
Expand Down Expand Up @@ -781,7 +781,8 @@ class GlobalObject : public Bun::GlobalScope {
JSC::JSWeakMap* napiTypeTags() const { return m_napiTypeTags.getInitializedOnMainThread(this); }

JSObject* cryptoObject() const { return m_cryptoObject.getInitializedOnMainThread(this); }
JSObject* JSDOMFileConstructor() const { return m_JSDOMFileConstructor.getInitializedOnMainThread(this); }
JSObject* JSDOMFileConstructor() const { return m_JSDOMFileClassStructure.constructorInitializedOnMainThread(this); }
Structure* JSDOMFileStructure() const { return m_JSDOMFileClassStructure.getInitializedOnMainThread(this); }

JSMap* nodeWorkerEnvironmentData() { return m_nodeWorkerEnvironmentData.get(); }
void setNodeWorkerEnvironmentData(JSMap* data);
Expand Down
Loading
Loading