diff options
author | 2023-02-13 04:37:12 -0800 | |
---|---|---|
committer | 2023-02-13 04:37:23 -0800 | |
commit | 37186f4b0a6b97ffb3ac1ec65ddc2146126b4545 (patch) | |
tree | 99bd379ab22c7531339c72c7d03f4fc9e6468b78 | |
parent | f310d7414b3a9863d99773317620e93c1e8b6693 (diff) | |
download | bun-37186f4b0a6b97ffb3ac1ec65ddc2146126b4545.tar.gz bun-37186f4b0a6b97ffb3ac1ec65ddc2146126b4545.tar.zst bun-37186f4b0a6b97ffb3ac1ec65ddc2146126b4545.zip |
Add pretty printer for FormData
-rw-r--r-- | src/bun.js/bindings/exports.zig | 14 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/JSDOMFormData.cpp | 75 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/JSDOMFormData.h | 3 | ||||
-rw-r--r-- | src/bun.js/webcore/response.zig | 1 | ||||
-rw-r--r-- | test/bun.js/FormData.test.ts | 9 |
5 files changed, 101 insertions, 1 deletions
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index b9dd350a6..09f52bfe3 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -2084,6 +2084,20 @@ pub const ZigConsoleClient = struct { } else if (value.as(JSC.WebCore.Blob)) |blob| { blob.writeFormat(this, writer_, enable_ansi_colors) catch {}; return; + } else if (value.as(JSC.DOMFormData) != null) { + const toJSONFunction = value.get(this.globalThis, "toJSON").?; + + this.addForNewLine("FormData (entries) ".len); + writer.writeAll(comptime Output.prettyFmt("<r><blue>FormData<r> <d>(entries)<r> ", enable_ansi_colors)); + + return this.printAs( + .Object, + Writer, + writer_, + toJSONFunction.callWithThis(this.globalThis, value, &.{}), + .Object, + enable_ansi_colors, + ); } else if (jsType != .DOMWrapper) { if (CAPI.JSObjectGetPrivate(value.asRef())) |private_data_ptr| { const priv_data = JSPrivateDataPtr.from(private_data_ptr); diff --git a/src/bun.js/bindings/webcore/JSDOMFormData.cpp b/src/bun.js/bindings/webcore/JSDOMFormData.cpp index 44c28bd00..3578db7ff 100644 --- a/src/bun.js/bindings/webcore/JSDOMFormData.cpp +++ b/src/bun.js/bindings/webcore/JSDOMFormData.cpp @@ -55,6 +55,7 @@ #include <wtf/PointerPreparations.h> #include <wtf/URL.h> #include "ZigGeneratedClasses.h" +#include "GCDefferalContext.h" namespace WebCore { using namespace JSC; @@ -100,6 +101,9 @@ static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_keys); static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_values); static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_forEach); +// Non-standard functions +static JSC_DECLARE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_toJSON); + // Attributes static JSC_DECLARE_CUSTOM_GETTER(jsDOMFormDataConstructor); @@ -191,6 +195,7 @@ static const HashTableValue JSDOMFormDataPrototypeTableValues[] = { { "keys"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_keys, 0 } }, { "values"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_values, 0 } }, { "forEach"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_forEach, 1 } }, + { "toJSON"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::NativeFunctionType, jsDOMFormDataPrototypeFunction_toJSON, 1 } }, }; const ClassInfo JSDOMFormDataPrototype::s_info = { "FormData"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFormDataPrototype) }; @@ -478,6 +483,76 @@ JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_set, (JSGlobalObject * l return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_setOverloadDispatcher>(*lexicalGlobalObject, *callFrame, "set"); } +/** + * Non standard function. + **/ +static inline JSC::EncodedJSValue jsDOMFormDataPrototypeFunction_toJSONBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSDOMFormData>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + auto& impl = castedThis->wrapped(); + size_t size = impl.count(); + JSObject* obj; + if (size == 0) { + obj = constructEmptyObject(lexicalGlobalObject); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + RELEASE_AND_RETURN(throwScope, JSValue::encode(obj)); + } else if (size < 64) { + obj = constructEmptyObject(lexicalGlobalObject, lexicalGlobalObject->objectPrototype(), size + 1); + } else { + obj = constructEmptyObject(lexicalGlobalObject); + } + + obj->putDirect(vm, vm.propertyNames->toStringTagSymbol, jsString(vm, "FormData"), PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + + auto iter = impl.items(); + WTF::HashSet<String> seenKeys; + + auto toJSValue = [&](const DOMFormData::FormDataEntryValue& entry) -> JSValue { + return toJS<IDLNullable<IDLUnion<IDLUSVString, IDLInterface<Blob>>>>(*lexicalGlobalObject, *castedThis->globalObject(), throwScope, entry); + }; + + for (auto& entry : iter) { + auto& key = entry.name; + auto& value = entry.data; + auto ident = Identifier::fromString(vm, key); + if (seenKeys.contains(key)) { + JSValue jsValue = obj->getDirect(vm, ident); + if (jsValue.isString() || jsValue.inherits<JSBlob>()) { + GCDeferralContext deferralContext(lexicalGlobalObject->vm()); + JSC::ObjectInitializationScope initializationScope(lexicalGlobalObject->vm()); + + JSC::JSArray* array = JSC::JSArray::tryCreateUninitializedRestricted( + initializationScope, &deferralContext, + lexicalGlobalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), + 2); + + array->initializeIndex(initializationScope, 0, jsValue); + array->initializeIndex(initializationScope, 1, toJSValue(value)); + obj->putDirect(vm, ident, array, 0); + } else if (jsValue.isObject() && jsValue.getObject()->inherits<JSC::JSArray>()) { + JSC::JSArray* array = jsCast<JSC::JSArray*>(jsValue.getObject()); + array->push(lexicalGlobalObject, toJSValue(value)); + RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); + + } else { + RELEASE_ASSERT_NOT_REACHED(); + } + } else { + seenKeys.add(key); + obj->putDirect(vm, ident, toJSValue(value), 0); + } + } + + RELEASE_AND_RETURN(throwScope, JSValue::encode(obj)); +} + +JSC_DEFINE_HOST_FUNCTION(jsDOMFormDataPrototypeFunction_toJSON, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + return IDLOperation<JSDOMFormData>::call<jsDOMFormDataPrototypeFunction_toJSONBody>(*lexicalGlobalObject, *callFrame, "toJSON"); +} + struct DOMFormDataIteratorTraits { static constexpr JSDOMIteratorType type = JSDOMIteratorType::Map; using KeyType = IDLUSVString; diff --git a/src/bun.js/bindings/webcore/JSDOMFormData.h b/src/bun.js/bindings/webcore/JSDOMFormData.h index 085c42c5d..511cbfd1b 100644 --- a/src/bun.js/bindings/webcore/JSDOMFormData.h +++ b/src/bun.js/bindings/webcore/JSDOMFormData.h @@ -45,7 +45,7 @@ public: 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(), JSC::NonArray); + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::JSType(WebCore::JSDOMWrapperType), StructureFlags), info(), JSC::NonArray); } static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*); @@ -57,6 +57,7 @@ public: } static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + protected: JSDOMFormData(JSC::Structure*, JSDOMGlobalObject&, Ref<DOMFormData>&&); diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 8af1c3958..de88cfca9 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -894,6 +894,7 @@ pub const Fetch = struct { var body_value = body_const; // TODO: buffer ReadableStream? // we have to explicitly check for InternalBlob + body = body_value.useAsAnyBlob(); } else { // an error was thrown diff --git a/test/bun.js/FormData.test.ts b/test/bun.js/FormData.test.ts index dbe1a8ef0..8e06aa96d 100644 --- a/test/bun.js/FormData.test.ts +++ b/test/bun.js/FormData.test.ts @@ -324,6 +324,15 @@ describe("FormData", () => { }); }); + it("Bun.inspect", () => { + const formData = new FormData(); + formData.append("foo", "bar"); + formData.append("foo", new Blob(["bar"])); + formData.append("bar", "baz"); + formData.append("boop", Bun.file("missing")); + expect(Bun.inspect(formData).length > 0).toBe(true); + }); + describe("URLEncoded", () => { test("should parse URL encoded", async () => { const response = new Response("foo=bar&baz=qux", { |