diff options
author | 2023-08-21 01:29:06 -0700 | |
---|---|---|
committer | 2023-08-21 01:29:06 -0700 | |
commit | c99a9ba33a36f16f79e38ff77423f5d301f6d2a8 (patch) | |
tree | 0ca11cdc93b665668c9c6d07da0ae865cd1f4c77 /src/bun.js | |
parent | f75b949524ac4a41e0f015cb981f356444fab331 (diff) | |
download | bun-c99a9ba33a36f16f79e38ff77423f5d301f6d2a8.tar.gz bun-c99a9ba33a36f16f79e38ff77423f5d301f6d2a8.tar.zst bun-c99a9ba33a36f16f79e38ff77423f5d301f6d2a8.zip |
Implement File
Diffstat (limited to 'src/bun.js')
-rw-r--r-- | src/bun.js/api/bun.zig | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/JSDOMFile.cpp | 105 | ||||
-rw-r--r-- | src/bun.js/bindings/JSDOMFile.h | 7 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 32 | ||||
-rw-r--r-- | src/bun.js/bindings/blob.cpp | 14 | ||||
-rw-r--r-- | src/bun.js/modules/NodeBufferModule.h | 7 | ||||
-rw-r--r-- | src/bun.js/webcore/blob.zig | 126 |
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); } |