aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-08-21 01:29:06 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-08-21 01:29:06 -0700
commitc99a9ba33a36f16f79e38ff77423f5d301f6d2a8 (patch)
tree0ca11cdc93b665668c9c6d07da0ae865cd1f4c77 /src
parentf75b949524ac4a41e0f015cb981f356444fab331 (diff)
downloadbun-c99a9ba33a36f16f79e38ff77423f5d301f6d2a8.tar.gz
bun-c99a9ba33a36f16f79e38ff77423f5d301f6d2a8.tar.zst
bun-c99a9ba33a36f16f79e38ff77423f5d301f6d2a8.zip
Implement File
Diffstat (limited to 'src')
-rw-r--r--src/bun.js/api/bun.zig2
-rw-r--r--src/bun.js/bindings/JSDOMFile.cpp105
-rw-r--r--src/bun.js/bindings/JSDOMFile.h7
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp32
-rw-r--r--src/bun.js/bindings/blob.cpp14
-rw-r--r--src/bun.js/modules/NodeBufferModule.h7
-rw-r--r--src/bun.js/webcore/blob.zig126
7 files changed, 285 insertions, 8 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index e98151fb1..8d1244195 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -15,7 +15,7 @@ pub const BunObject = struct {
pub const build = Bun.JSBundler.buildFn;
pub const connect = JSC.wrapStaticMethod(JSC.API.Listener, "connect", false);
pub const deflateSync = JSC.wrapStaticMethod(JSZlib, "deflateSync", true);
- pub const file = WebCore.Blob.constructFile;
+ pub const file = WebCore.Blob.constructBunFile;
pub const fs = Bun.fs;
pub const gc = Bun.runGC;
pub const generateHeapSnapshot = Bun.generateHeapSnapshot;
diff --git a/src/bun.js/bindings/JSDOMFile.cpp b/src/bun.js/bindings/JSDOMFile.cpp
new file mode 100644
index 000000000..1d7770ac1
--- /dev/null
+++ b/src/bun.js/bindings/JSDOMFile.cpp
@@ -0,0 +1,105 @@
+#include "root.h"
+#include "ZigGeneratedClasses.h"
+#include "JavaScriptCore/ObjectConstructor.h"
+#include "JavaScriptCore/InternalFunction.h"
+#include "JavaScriptCore/FunctionPrototype.h"
+#include "JSDOMFile.h"
+
+using namespace JSC;
+
+extern "C" void* JSDOMFile__construct(JSC::JSGlobalObject*, JSC::CallFrame* callframe);
+extern "C" 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;
+
+public:
+ JSDOMFile(JSC::VM& vm, JSC::Structure* structure)
+ : Base(vm, structure, nullptr, construct)
+ {
+ }
+
+ DECLARE_INFO;
+
+ static constexpr unsigned StructureFlags = (Base::StructureFlags & ~ImplementsDefaultHasInstance) | ImplementsHasInstance;
+
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ 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());
+ }
+
+ void finishCreation(JSC::VM& vm)
+ {
+ Base::finishCreation(vm, 2, "File"_s);
+ }
+
+ static JSDOMFile* create(JSC::VM& vm, JSGlobalObject* globalObject)
+ {
+ auto* zigGlobal = reinterpret_cast<Zig::GlobalObject*>(globalObject);
+ auto* object = new (NotNull, JSC::allocateCell<JSDOMFile>(vm)) JSDOMFile(vm, createStructure(vm, globalObject, zigGlobal->functionPrototype()));
+ object->finishCreation(vm);
+
+ // 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);
+
+ return object;
+ }
+
+ static bool customHasInstance(JSObject* object, JSGlobalObject* globalObject, JSValue value)
+ {
+ if (!value.isObject())
+ return false;
+
+ // 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 EncodedJSValue construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
+ {
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ JSC::VM& vm = globalObject->vm();
+ JSObject* newTarget = asObject(callFrame->newTarget());
+ auto* constructor = globalObject->JSDOMFileConstructor();
+ Structure* structure = globalObject->JSBlobStructure();
+ 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->JSBlobStructure());
+ }
+
+ void* ptr = JSDOMFile__construct(globalObject, callFrame);
+
+ if (UNLIKELY(!ptr)) {
+ return JSValue::encode(JSC::jsUndefined());
+ }
+
+ return JSValue::encode(
+ WebCore::JSBlob::create(vm, globalObject, structure, ptr));
+ }
+};
+
+const JSC::ClassInfo JSDOMFile::s_info = { "File"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFile) };
+
+namespace Bun {
+
+JSC::JSObject* createJSDOMFileConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ return JSDOMFile::create(vm, globalObject);
+}
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/JSDOMFile.h b/src/bun.js/bindings/JSDOMFile.h
new file mode 100644
index 000000000..eed1e98ab
--- /dev/null
+++ b/src/bun.js/bindings/JSDOMFile.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "root.h"
+
+namespace Bun {
+JSC::JSObject* createJSDOMFileConstructor(JSC::VM&, JSC::JSGlobalObject*);
+}
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index f8fc2ea2b..0ecafeae4 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -123,6 +123,8 @@
#include "JSMessagePort.h"
#include "JSBroadcastChannel.h"
+#include "JSDOMFile.h"
+
#if ENABLE(REMOTE_INSPECTOR)
#include "JavaScriptCore/RemoteInspectorServer.h"
#endif
@@ -2780,6 +2782,12 @@ void GlobalObject::finishCreation(VM& vm)
Base::finishCreation(vm);
ASSERT(inherits(info()));
+ 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;
@@ -3330,6 +3338,26 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPostMessage,
return JSValue::encode(jsUndefined());
}
+JSC_DEFINE_CUSTOM_GETTER(JSDOMFileConstructor_getter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
+{
+ Zig::GlobalObject* bunGlobalObject = jsCast<Zig::GlobalObject*>(globalObject);
+ return JSValue::encode(
+ bunGlobalObject->JSDOMFileConstructor());
+}
+
+JSC_DEFINE_CUSTOM_SETTER(JSDOMFileConstructor_setter,
+ (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
+ JSC::EncodedJSValue value, JSC::PropertyName property))
+{
+ if (JSValue::decode(thisValue) != globalObject) {
+ return false;
+ }
+
+ auto& vm = globalObject->vm();
+ globalObject->putDirect(vm, property, JSValue::decode(value), 0);
+ return true;
+}
+
JSC_DEFINE_CUSTOM_GETTER(BunCommonJSModule_getter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
{
Zig::GlobalObject* bunGlobalObject = jsCast<Zig::GlobalObject*>(globalObject);
@@ -3777,6 +3805,9 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "Crypto"_s), JSC::CustomGetterSetter::create(vm, JSCrypto_getter, JSCrypto_setter),
JSC::PropertyAttribute::DontDelete | 0);
+ putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "File"_s), JSC::CustomGetterSetter::create(vm, JSDOMFileConstructor_getter, JSDOMFileConstructor_setter),
+ JSC::PropertyAttribute::DontDelete | 0);
+
putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "DOMException"_s), JSC::CustomGetterSetter::create(vm, JSDOMException_getter, nullptr),
JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
@@ -3996,6 +4027,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_emitReadableNextTickFunction.visit(visitor);
thisObject->m_JSBufferSubclassStructure.visit(visitor);
thisObject->m_cryptoObject.visit(visitor);
+ thisObject->m_JSDOMFileConstructor.visit(visitor);
thisObject->m_requireFunctionUnbound.visit(visitor);
thisObject->m_requireResolveFunctionUnbound.visit(visitor);
diff --git a/src/bun.js/bindings/blob.cpp b/src/bun.js/bindings/blob.cpp
index 257f230e1..0f255d7c8 100644
--- a/src/bun.js/bindings/blob.cpp
+++ b/src/bun.js/bindings/blob.cpp
@@ -1,17 +1,29 @@
#include "blob.h"
+#include "ZigGeneratedClasses.h"
extern "C" JSC::EncodedJSValue Blob__create(JSC::JSGlobalObject* globalObject, void* impl);
+extern "C" void* Blob__setAsFile(void* impl, BunString* filename);
namespace WebCore {
JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, WebCore::Blob& impl)
{
+ BunString filename = Bun::toString(impl.fileName());
+ impl.m_impl = Blob__setAsFile(impl.impl(), &filename);
+
return JSC::JSValue::decode(Blob__create(lexicalGlobalObject, Blob__dupe(impl.impl())));
}
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Ref<WebCore::Blob>&& impl)
{
- return JSC::JSValue::decode(Blob__create(lexicalGlobalObject, impl->impl()));
+ auto fileNameStr = impl->fileName();
+ BunString filename = Bun::toString(fileNameStr);
+
+ EncodedJSValue encoded = Blob__create(lexicalGlobalObject, impl->impl());
+ JSBlob* blob = jsCast<JSBlob*>(JSC::JSValue::decode(encoded));
+ Blob__setAsFile(blob->wrapped(), &filename);
+
+ return JSC::JSValue::decode(encoded);
}
} \ No newline at end of file
diff --git a/src/bun.js/modules/NodeBufferModule.h b/src/bun.js/modules/NodeBufferModule.h
index 5c6acd48e..5eea9c099 100644
--- a/src/bun.js/modules/NodeBufferModule.h
+++ b/src/bun.js/modules/NodeBufferModule.h
@@ -149,12 +149,11 @@ DEFINE_NATIVE_MODULE(NodeBuffer) {
put(JSC::Identifier::fromString(vm, "SlowBuffer"_s), slowBuffer);
auto blobIdent = JSC::Identifier::fromString(vm, "Blob"_s);
- JSValue blobValue =
- lexicalGlobalObject->get(globalObject, PropertyName(blobIdent));
+ JSValue blobValue = globalObject->JSBlobConstructor();
put(blobIdent, blobValue);
- // TODO: implement File
- put(JSC::Identifier::fromString(vm, "File"_s), blobValue);
+ put(JSC::Identifier::fromString(vm, "File"_s),
+ globalObject->JSDOMFileConstructor());
put(JSC::Identifier::fromString(vm, "INSPECT_MAX_BYTES"_s),
JSC::jsNumber(50));
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index 983841581..df2e17ce4 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -88,8 +88,13 @@ pub const Blob = struct {
/// When UTF-16, they're nearly always due to non-ascii characters
is_all_ascii: ?bool = null,
+ /// Was it created via file constructor?
+ is_jsdom_file: bool = false,
+
globalThis: *JSGlobalObject = undefined,
+ last_modified: f64 = 0.0,
+
/// Max int of double precision
/// 9 petabytes is probably enough for awhile
/// We want to avoid coercing to a BigInt because that's a heap allocation
@@ -478,6 +483,22 @@ pub const Blob = struct {
return Blob__dupe(this);
}
+ export fn Blob__setAsFile(this: *Blob, path_str: *bun.String) *Blob {
+ this.is_jsdom_file = true;
+
+ // This is not 100% correct...
+ if (this.store) |store| {
+ if (store.data == .bytes) {
+ if (store.data.bytes.stored_name.len == 0) {
+ var utf8 = path_str.toUTF8WithoutRef(bun.default_allocator).clone(bun.default_allocator) catch unreachable;
+ store.data.bytes.stored_name = bun.PathString.init(utf8.slice());
+ }
+ }
+ }
+
+ return this;
+ }
+
export fn Blob__dupe(ptr: *anyopaque) *Blob {
var this = bun.cast(*Blob, ptr);
var new = bun.default_allocator.create(Blob) catch unreachable;
@@ -494,6 +515,7 @@ pub const Blob = struct {
_ = Blob__dupeFromJS;
_ = Blob__destroy;
_ = Blob__dupe;
+ _ = Blob__setAsFile;
}
pub fn writeFormatForSize(size: usize, writer: anytype, comptime enable_ansi_colors: bool) !void {
@@ -1123,7 +1145,102 @@ pub const Blob = struct {
return JSC.JSPromise.resolvedPromiseValue(globalThis, JSC.JSValue.jsNumber(written));
}
- pub fn constructFile(
+ pub export fn JSDOMFile__hasInstance(_: JSC.JSValue, _: *JSC.JSGlobalObject, value: JSC.JSValue) callconv(.C) bool {
+ JSC.markBinding(@src());
+ var blob = value.as(Blob) orelse return false;
+ return blob.is_jsdom_file;
+ }
+
+ pub export fn JSDOMFile__construct(
+ globalThis: *JSC.JSGlobalObject,
+ callframe: *JSC.CallFrame,
+ ) callconv(.C) ?*Blob {
+ JSC.markBinding(@src());
+ var allocator = bun.default_allocator;
+ var blob: Blob = undefined;
+ var arguments = callframe.arguments(3);
+ var args = arguments.ptr[0..arguments.len];
+
+ if (args.len < 2) {
+ globalThis.throwInvalidArguments("new File(bits, name) expects at least 2 arguments", .{});
+ return null;
+ }
+
+ const name_value_str = bun.String.tryFromJS(args[1], globalThis) orelse {
+ globalThis.throwInvalidArguments("new File(bits, name) expects string as the second argument", .{});
+ return null;
+ };
+
+ blob = get(globalThis, args[0], false, true) catch |err| {
+ if (err == error.InvalidArguments) {
+ globalThis.throwInvalidArguments("new File(bits, name) expects iterable as the first argument", .{});
+ return null;
+ }
+ globalThis.throwOutOfMemory();
+ return null;
+ };
+
+ if (blob.store) |store_| {
+ store_.data.bytes.stored_name = bun.PathString.init(
+ (name_value_str.toUTF8WithoutRef(bun.default_allocator).clone(bun.default_allocator) catch unreachable).slice(),
+ );
+ }
+
+ if (args.len > 2) {
+ const options = args[2];
+ if (options.isObject()) {
+ // type, the ASCII-encoded string in lower case
+ // representing the media type of the Blob.
+ // Normative conditions for this member are provided
+ // in the § 3.1 Constructors.
+ if (options.get(globalThis, "type")) |content_type| {
+ inner: {
+ if (content_type.isString()) {
+ var content_type_str = content_type.toSlice(globalThis, bun.default_allocator);
+ defer content_type_str.deinit();
+ var slice = content_type_str.slice();
+ if (!strings.isAllASCII(slice)) {
+ break :inner;
+ }
+ blob.content_type_was_set = true;
+
+ if (globalThis.bunVM().mimeType(slice)) |mime| {
+ blob.content_type = mime.value;
+ break :inner;
+ }
+ var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable;
+ blob.content_type = strings.copyLowercase(slice, content_type_buf);
+ blob.content_type_allocated = true;
+ }
+ }
+ }
+
+ if (options.getTruthy(globalThis, "lastModified")) |last_modified| {
+ blob.last_modified = last_modified.coerce(f64, globalThis);
+ }
+ }
+ }
+
+ if (blob.content_type.len == 0) {
+ blob.content_type = "";
+ blob.content_type_was_set = false;
+ }
+
+ var blob_ = allocator.create(Blob) catch unreachable;
+ blob_.* = blob;
+ blob_.allocator = allocator;
+ blob_.is_jsdom_file = true;
+ return blob_;
+ }
+
+ comptime {
+ if (!JSC.is_bindgen) {
+ _ = JSDOMFile__hasInstance;
+ _ = JSDOMFile__construct;
+ }
+ }
+
+ pub fn constructBunFile(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
@@ -2484,7 +2601,7 @@ pub const Blob = struct {
cap: SizeType = 0,
allocator: std.mem.Allocator,
- /// Used by standalone module graph
+ /// Used by standalone module graph and the File constructor
stored_name: bun.PathString = bun.PathString.empty,
pub fn init(bytes: []u8, allocator: std.mem.Allocator) ByteStore {
@@ -2505,6 +2622,7 @@ pub const Blob = struct {
}
pub fn deinit(this: *ByteStore) void {
+ bun.default_allocator.free(this.stored_name.slice());
this.allocator.free(this.ptr[0..this.cap]);
}
@@ -2911,6 +3029,10 @@ pub const Blob = struct {
}
}
+ if (this.is_jsdom_file) {
+ return JSValue.jsNumber(this.last_modified);
+ }
+
return JSValue.jsNumber(init_timestamp);
}