diff options
author | 2022-03-13 06:08:10 -0700 | |
---|---|---|
committer | 2022-03-13 06:08:10 -0700 | |
commit | f4504292cfd1e11bc6b4879a8b184aa6b842c8cb (patch) | |
tree | 2e0bb0f4596e739b37f76512dbc641f6102d8bcd | |
parent | 6d71749c703d0061885230438ce5e46838700c59 (diff) | |
download | bun-f4504292cfd1e11bc6b4879a8b184aa6b842c8cb.tar.gz bun-f4504292cfd1e11bc6b4879a8b184aa6b842c8cb.tar.zst bun-f4504292cfd1e11bc6b4879a8b184aa6b842c8cb.zip |
[bun.js] Implement `Blob`
-rw-r--r-- | integration/bunjs-only-snippets/fetch.test.js | 118 | ||||
-rw-r--r-- | src/http_client_async.zig | 1 | ||||
-rw-r--r-- | src/javascript/jsc/api/router.zig | 97 | ||||
-rw-r--r-- | src/javascript/jsc/base.zig | 55 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.cpp | 22 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/bindings.zig | 42 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.h | 4 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/headers.zig | 2 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/helpers.h | 2 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 16 | ||||
-rw-r--r-- | src/javascript/jsc/node/types.zig | 1 | ||||
-rw-r--r-- | src/javascript/jsc/test/jest.zig | 16 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 1558 | ||||
-rw-r--r-- | src/js_ast.zig | 188 | ||||
-rw-r--r-- | src/resolver/resolver.zig | 2 | ||||
-rw-r--r-- | src/string_joiner.zig | 37 |
17 files changed, 1537 insertions, 626 deletions
diff --git a/integration/bunjs-only-snippets/fetch.test.js b/integration/bunjs-only-snippets/fetch.test.js index 76e09bcf8..cfe24d5ac 100644 --- a/integration/bunjs-only-snippets/fetch.test.js +++ b/integration/bunjs-only-snippets/fetch.test.js @@ -21,6 +21,82 @@ describe("fetch", () => { } }); +function testBlobInterface(blobbyConstructor, hasBlobFn) { + it("json", async () => { + var response = blobbyConstructor(JSON.stringify({ hello: true })); + expect(JSON.stringify(await response.json())).toBe( + JSON.stringify({ hello: true }) + ); + }); + it("text", async () => { + var response = blobbyConstructor(JSON.stringify({ hello: true })); + expect(await response.text()).toBe(JSON.stringify({ hello: true })); + }); + it("arrayBuffer", async () => { + var response = blobbyConstructor(JSON.stringify({ hello: true })); + + const bytes = new TextEncoder().encode(JSON.stringify({ hello: true })); + const compare = new Uint8Array(await response.arrayBuffer()); + for (let i = 0; i < compare.length; i++) { + expect(compare[i]).toBe(bytes[i]); + } + }); + hasBlobFn && + it("blob", async () => { + var response = blobbyConstructor(JSON.stringify({ hello: true })); + const size = JSON.stringify({ hello: true }).length; + const blobed = await response.blob(); + expect(blobed instanceof Blob).toBe(true); + expect(blobed.size).toBe(size); + expect(blobed.type).toBe(""); + blobed.type = "application/json"; + expect(blobed.type).toBe("application/json"); + }); +} + +describe("Blob", () => { + testBlobInterface((data) => new Blob([data])); + + var blobConstructorValues = [ + ["123", "456"], + ["123", 456], + ["123", "456", "789"], + ["123", 456, 789], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + [Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 9])], + [Uint8Array.from([1, 2, 3, 4]), "5678", 9], + [new Blob([Uint8Array.from([1, 2, 3, 4])]), "5678", 9], + ]; + var expected = [ + "123456", + "123456", + "123456789", + "123456789", + "123456789", + "\x01\x02\x03\x04\x05\x06\x07\t", + "\x01\x02\x03\x0456789", + "\x01\x02\x03\x0456789", + ]; + + it(`blobConstructorValues`, async () => { + for (let i = 0; i < blobConstructorValues.length; i++) { + var response = new Blob(blobConstructorValues[i]); + const res = await response.text(); + if (res !== expected[i]) { + throw new Error( + `Failed: ${expected[i] + .split("") + .map((a) => a.charCodeAt(0))}, received: ${res + .split("") + .map((a) => a.charCodeAt(0))}` + ); + } + + expect(res).toBe(expected[i]); + } + }); +}); + describe("Response", () => { it("clone", async () => { var body = new Response("<div>hello</div>", { @@ -34,4 +110,46 @@ describe("Response", () => { expect(body.headers.get("content-type")).toBe("text/plain"); expect(await clone.text()).toBe("<div>hello</div>"); }); + testBlobInterface((data) => new Response(data), true); +}); + +describe("Request", () => { + it("clone", async () => { + var body = new Request("https://hello.com", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: "<div>hello</div>", + }); + expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8"); + + var clone = body.clone(); + body.headers.set("content-type", "text/plain"); + expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); + expect(body.headers.get("content-type")).toBe("text/plain"); + expect(await clone.text()).toBe("<div>hello</div>"); + }); + + testBlobInterface( + (data) => new Request("https://hello.com", { body: data }), + true + ); +}); + +describe("Headers", () => { + it("writes", async () => { + var body = new Request("https://hello.com", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: "<div>hello</div>", + }); + expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8"); + + var clone = body.clone(); + body.headers.set("content-type", "text/plain"); + expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); + expect(body.headers.get("content-type")).toBe("text/plain"); + expect(await clone.text()).toBe("<div>hello</div>"); + }); }); diff --git a/src/http_client_async.zig b/src/http_client_async.zig index 8512b60cc..a5ca73cf3 100644 --- a/src/http_client_async.zig +++ b/src/http_client_async.zig @@ -30,6 +30,7 @@ const AsyncSocket = @import("./http/async_socket.zig"); const ZlibPool = @import("./http/zlib.zig"); const URLBufferPool = ObjectPool([4096]u8, null, false, 10); pub const MimeType = @import("./http/mime_type.zig"); +pub const URLPath = @import("./http/url_path.zig"); // This becomes Arena.allocator pub var default_allocator: std.mem.Allocator = undefined; pub var default_arena: Arena = undefined; diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig index 500ce1853..ed2e0a9af 100644 --- a/src/javascript/jsc/api/router.zig +++ b/src/javascript/jsc/api/router.zig @@ -28,10 +28,15 @@ const To = Base.To; const Request = WebCore.Request; const d = Base.d; const FetchEvent = WebCore.FetchEvent; - +const URLPath = @import("../../../http/url_path.zig"); +const URL = @import("../../../query_string_map.zig").URL; route: *const FilesystemRouter.Match, +route_holder: FilesystemRouter.Match = undefined, +needs_deinit: bool = false, query_string_map: ?QueryStringMap = null, param_map: ?QueryStringMap = null, +params_list_holder: FilesystemRouter.Param.List = .{}, + script_src: ?string = null, script_src_buf: [1024]u8 = undefined, @@ -65,42 +70,101 @@ pub fn match( return null; } - if (js.JSValueIsObjectOfClass(ctx, arguments[0], FetchEvent.Class.get().*)) { + if (FetchEvent.Class.loaded and js.JSValueIsObjectOfClass(ctx, arguments[0], FetchEvent.Class.get().*)) { return matchFetchEvent(ctx, To.Zig.ptr(FetchEvent, arguments[0]), exception); } - // if (js.JSValueIsString(ctx, arguments[0])) { - // return matchPathName(ctx, arguments[0], exception); - // } + var router = JavaScript.VirtualMachine.vm.bundler.router orelse { + JSError(getAllocator(ctx), "Bun.match needs a framework configured with routes", .{}, ctx, exception); + return null; + }; + var arg = JSC.JSValue.fromRef(arguments[0]); + var path_: ?ZigString.Slice = null; + var pathname: string = ""; + defer { + if (path_) |path| { + path.deinit(); + } + } + + if (arg.isString()) { + var path_string = arg.getZigString(ctx.ptr()); + path_ = path_string.toSlice(bun.default_allocator); + var url = URL.parse(path_.?.slice()); + pathname = url.pathname; + } else if (arg.as(Request)) |req| { + var path_string = req.url; + path_ = path_string.toSlice(bun.default_allocator); + var url = URL.parse(path_.?.slice()); + pathname = url.pathname; + } + + if (path_ == null) { + JSError(getAllocator(ctx), "Expected string, FetchEvent, or Request", .{}, ctx, exception); + return null; + } + + const url_path = URLPath.parse(path_.?.slice()) catch { + JSError(getAllocator(ctx), "Could not parse URL path", .{}, ctx, exception); + return null; + }; - if (js.JSValueIsObjectOfClass(ctx, arguments[0], Request.Class.get().*)) { - return matchRequest(ctx, To.Zig.ptr(Request, arguments[0]), exception); + var match_params_fallback = std.heap.stackFallback(1024, bun.default_allocator); + var match_params_allocator = match_params_fallback.get(); + var match_params = FilesystemRouter.Param.List{}; + match_params.ensureTotalCapacity(match_params_allocator, 16) catch unreachable; + var prev_allocator = router.routes.allocator; + router.routes.allocator = match_params_allocator; + defer router.routes.allocator = prev_allocator; + if (router.routes.matchPage("", url_path, &match_params)) |matched| { + var match_ = matched; + var params_list = match_.params.clone(bun.default_allocator) catch unreachable; + var instance = getAllocator(ctx).create(Router) catch unreachable; + + instance.* = Router{ + .route_holder = match_, + .route = undefined, + }; + instance.params_list_holder = params_list; + instance.route = &instance.route_holder; + instance.route_holder.params = &instance.params_list_holder; + instance.script_src_buf_writer = ScriptSrcStream{ .pos = 0, .buffer = std.mem.span(&instance.script_src_buf) }; + + return Instance.make(ctx, instance); } + // router.routes.matchPage - return null; + return JSC.JSValue.jsNull().asObjectRef(); } fn matchRequest( ctx: js.JSContextRef, request: *const Request, - exception: js.ExceptionRef, + _: js.ExceptionRef, ) js.JSObjectRef { - return createRouteObject(ctx, request.request_context, exception); + return createRouteObject(ctx, request.request_context); } fn matchFetchEvent( ctx: js.JSContextRef, fetch_event: *const FetchEvent, - exception: js.ExceptionRef, + _: js.ExceptionRef, ) js.JSObjectRef { - return createRouteObject(ctx, fetch_event.request_context, exception); + return createRouteObject(ctx, fetch_event.request_context); } -fn createRouteObject(ctx: js.JSContextRef, req: *const http.RequestContext, _: js.ExceptionRef) js.JSValueRef { +fn createRouteObject(ctx: js.JSContextRef, req: *const http.RequestContext) js.JSValueRef { const route = &(req.matched_route orelse { return js.JSValueMakeNull(ctx); }); + return createRouteObjectFromMatch(ctx, route); +} + +fn createRouteObjectFromMatch( + ctx: js.JSContextRef, + route: *const FilesystemRouter.Match, +) js.JSValueRef { var router = getAllocator(ctx).create(Router) catch unreachable; router.* = Router{ .route = route, @@ -276,6 +340,13 @@ pub fn finalize( if (this.query_string_map) |*map| { map.deinit(); } + + if (this.needs_deinit) { + this.params_list_holder.deinit(bun.default_allocator); + this.params_list_holder = .{}; + this.needs_deinit = false; + bun.default_allocator.destroy(this); + } } pub fn getPathname( diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig index cfdc6c110..5d1dbcc31 100644 --- a/src/javascript/jsc/base.zig +++ b/src/javascript/jsc/base.zig @@ -1720,6 +1720,17 @@ pub const ArrayBuffer = extern struct { } pub fn toJS(this: ArrayBuffer, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.JSValue { + if (this.typed_array_type == .ArrayBuffer) { + return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArrayBufferWithBytesNoCopy( + ctx, + this.ptr, + this.byte_len, + MarkedArrayBuffer_deallocator, + @intToPtr(*anyopaque, @ptrToInt(&bun.default_allocator)), + exception, + )); + } + return JSC.JSValue.fromRef(JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy( ctx, this.typed_array_type.toC(), @@ -1865,39 +1876,41 @@ const DocType = JSC.Cloudflare.DocType; const EndTag = JSC.Cloudflare.EndTag; const DocEnd = JSC.Cloudflare.DocEnd; const AttributeIterator = JSC.Cloudflare.AttributeIterator; +const Blob = JSC.WebCore.Blob; pub const JSPrivateDataPtr = TaggedPointerUnion(.{ - ResolveError, + AttributeIterator, + BigIntStats, + Blob, + Body, BuildError, - Response, - Request, + Comment, + DescribeScope, + DirEnt, + DocEnd, + DocType, + Element, + EndTag, + Expect, + ExpectPrototype, FetchEvent, + FetchTaskletContext, Headers, - Body, - Router, + HTMLRewriter, JSNode, LazyPropertiesObject, ModuleNamespace, - FetchTaskletContext, - DescribeScope, - Expect, - ExpectPrototype, NodeFS, + Request, + ResolveError, + Response, + Router, Stats, - BigIntStats, - DirEnt, - Transpiler, - TextEncoder, + TextChunk, TextDecoder, + TextEncoder, TimeoutTask, - HTMLRewriter, - Element, - Comment, - TextChunk, - DocType, - EndTag, - DocEnd, - AttributeIterator, + Transpiler, }); pub inline fn GetJSPrivateData(comptime Type: type, ref: js.JSObjectRef) ?*Type { diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp index 40baf6c01..bfdb24600 100644 --- a/src/javascript/jsc/bindings/bindings.cpp +++ b/src/javascript/jsc/bindings/bindings.cpp @@ -1,4 +1,5 @@ #include "BunClientData.h" +#include "GCDefferalContext.h" #include "ZigGlobalObject.h" #include "helpers.h" #include "root.h" @@ -42,7 +43,6 @@ #include <wtf/text/StringImpl.h> #include <wtf/text/StringView.h> #include <wtf/text/WTFString.h> - extern "C" { JSC__JSValue SystemError__toErrorInstance(const SystemError* arg0, @@ -237,12 +237,20 @@ void JSC__JSValue__jsonStringify(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg unsigned char JSC__JSValue__jsType(JSC__JSValue JSValue0) { JSC::JSValue jsValue = JSC::JSValue::decode(JSValue0); - if (JSC::JSCell* cell = jsValue.asCell()) - return cell->type(); + // if the value is NOT a cell + // asCell will return an invalid pointer rather than a nullptr + if (jsValue.isCell()) + return jsValue.asCell()->type(); return 0; } +JSC__JSValue JSC__JSValue__parseJSON(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1) +{ + JSC::JSValue jsValue = JSC::JSValue::decode(JSValue0); + return JSC::JSValue::encode(JSC::JSONParse(arg1, jsValue.toWTFString(arg1))); +} + void JSC__JSGlobalObject__deleteModuleRegistryEntry(JSC__JSGlobalObject* global, ZigString* arg1) { JSC::JSMap* map = JSC::jsDynamicCast<JSC::JSMap*>( @@ -2148,6 +2156,14 @@ void JSC__VM__holdAPILock(JSC__VM* arg0, void* ctx, void (*callback)(void* arg0) callback(ctx); } +void JSC__VM__deferGC(JSC__VM* vm, void* ctx, void (*callback)(void* arg0)) +{ + JSC::GCDeferralContext deferralContext(reinterpret_cast<JSC__VM&>(vm)); + JSC::DisallowGC disallowGC; + + callback(ctx); +} + void JSC__VM__deleteAllCode(JSC__VM* arg1, JSC__JSGlobalObject* globalObject) { JSC::JSLockHolder locker(globalObject->vm()); diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig index 6215d9e4a..f33d82ad6 100644 --- a/src/javascript/jsc/bindings/bindings.zig +++ b/src/javascript/jsc/bindings/bindings.zig @@ -88,6 +88,18 @@ pub const ZigString = extern struct { ptr: [*]const u8, len: usize, + pub fn clone(this: ZigString, allocator: std.mem.Allocator) !ZigString { + var sliced = this.toSlice(allocator); + if (!sliced.allocated) { + var str = ZigString.init(try allocator.dupe(u8, sliced.slice())); + str.mark(); + str.markUTF8(); + return str; + } + + return this; + } + pub const shim = Shimmer("", "ZigString", @This()); pub const Slice = struct { @@ -106,7 +118,7 @@ pub const ZigString = extern struct { return @intToPtr([*]u8, @ptrToInt(this.ptr))[0..this.len]; } - pub fn deinit(this: *Slice) void { + pub fn deinit(this: *const Slice) void { if (!this.allocated) { return; } @@ -261,7 +273,7 @@ pub const ZigString = extern struct { } pub fn trimmedSlice(this: *const ZigString) []const u8 { - return std.mem.trim(u8, this.ptr[0..@minimum(this.len, std.math.maxInt(u32))], " \r\n"); + return strings.trim(this.ptr[0..@minimum(this.len, std.math.maxInt(u32))], " \r\n"); } pub fn toValueAuto(this: *const ZigString, global: *JSGlobalObject) JSValue { @@ -1603,6 +1615,7 @@ pub const JSValue = enum(u64) { GetterSetter, CustomGetterSetter, + /// For 32-bit architectures, this wraps a 64-bit JSValue APIValueWrapper, NativeExecutable, @@ -1824,6 +1837,16 @@ pub const JSValue = enum(u64) { return cppFn("jsType", .{this}); } + pub fn jsTypeLoose( + this: JSValue, + ) JSType { + if (this.isNumber()) { + return JSType.NumberObject; + } + + return this.jsType(); + } + pub fn createEmptyObject(global: *JSGlobalObject, len: usize) JSValue { return cppFn("createEmptyObject", .{ global, len }); } @@ -2230,6 +2253,13 @@ pub const JSValue = enum(u64) { }); } + pub fn parseJSON(this: JSValue, globalObject: *JSGlobalObject) JSValue { + return cppFn("parseJSON", .{ + this, + globalObject, + }); + } + pub inline fn asRef(this: JSValue) C_API.JSValueRef { return @intToPtr(C_API.JSValueRef, @intCast(usize, @enumToInt(this))); } @@ -2246,7 +2276,7 @@ pub const JSValue = enum(u64) { return @intToPtr(*anyopaque, @enumToInt(this)); } - pub const Extern = [_][]const u8{ "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "getReadableStreamState", "getWritableStreamState", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" }; + pub const Extern = [_][]const u8{ "parseJSON", "symbolKeyFor", "symbolFor", "getSymbolDescription", "createInternalPromise", "asInternalPromise", "asArrayBuffer_", "getReadableStreamState", "getWritableStreamState", "fromEntries", "createTypeError", "createRangeError", "createObject2", "getIfPropertyExistsImpl", "jsType", "jsonStringify", "kind_", "isTerminationException", "isSameValue", "getLengthOfArray", "toZigString", "createStringArray", "createEmptyObject", "putRecord", "asPromise", "isClass", "getNameProperty", "getClassName", "getErrorsProperty", "toInt32", "toBoolean", "isInt32", "isIterable", "forEach", "isAggregateError", "toZigException", "isException", "toWTFString", "hasProperty", "getPropertyNames", "getDirect", "putDirect", "getIfExists", "asString", "asObject", "asNumber", "isError", "jsNull", "jsUndefined", "jsTDZValue", "jsBoolean", "jsDoubleNumber", "jsNumberFromDouble", "jsNumberFromChar", "jsNumberFromU16", "jsNumberFromInt32", "jsNumberFromInt64", "jsNumberFromUint64", "isUndefined", "isNull", "isUndefinedOrNull", "isBoolean", "isAnyInt", "isUInt32AsAnyInt", "isInt32AsAnyInt", "isNumber", "isString", "isBigInt", "isHeapBigInt", "isBigInt32", "isSymbol", "isPrimitive", "isGetterSetter", "isCustomGetterSetter", "isObject", "isCell", "asCell", "toString", "toStringOrNull", "toPropertyKey", "toPropertyKeyValue", "toObject", "toString", "getPrototype", "getPropertyByPropertyName", "eqlValue", "eqlCell", "isCallable" }; }; extern "c" fn Microtask__run(*Microtask, *JSGlobalObject) void; @@ -2386,6 +2416,10 @@ pub const VM = extern struct { cppFn("holdAPILock", .{ this, ctx, callback }); } + pub fn deferGC(this: *VM, ctx: ?*anyopaque, callback: fn (ctx: ?*anyopaque) callconv(.C) void) void { + cppFn("deferGC", .{ this, ctx, callback }); + } + pub fn deleteAllCode( vm: *VM, global_object: *JSGlobalObject, @@ -2459,7 +2493,7 @@ pub const VM = extern struct { }); } - pub const Extern = [_][]const u8{ "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" }; + pub const Extern = [_][]const u8{ "deferGC", "holdAPILock", "runGC", "generateHeapSnapshot", "isJITEnabled", "deleteAllCode", "create", "deinit", "setExecutionForbidden", "executionForbidden", "isEntered", "throwError", "drainMicrotasks", "whenIdle", "shrinkFootprint", "setExecutionTimeLimit", "clearExecutionTimeLimit" }; }; pub const ThrowScope = extern struct { diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h index e3c04d000..c620b6e98 100644 --- a/src/javascript/jsc/bindings/headers-cpp.h +++ b/src/javascript/jsc/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1647061500 +//-- AUTOGENERATED FILE -- 1647165586 // clang-format off #pragma once diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h index e87cb4377..f63a87319 100644 --- a/src/javascript/jsc/bindings/headers.h +++ b/src/javascript/jsc/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format: off -//-- AUTOGENERATED FILE -- 1647061500 +//-- AUTOGENERATED FILE -- 1647165586 #pragma once #include <stddef.h> @@ -490,6 +490,7 @@ CPP_DECL void JSC__JSValue__jsonStringify(JSC__JSValue JSValue0, JSC__JSGlobalOb CPP_DECL JSC__JSValue JSC__JSValue__jsTDZValue(); CPP_DECL unsigned char JSC__JSValue__jsType(JSC__JSValue JSValue0); CPP_DECL JSC__JSValue JSC__JSValue__jsUndefined(); +CPP_DECL JSC__JSValue JSC__JSValue__parseJSON(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__JSValue__putRecord(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4); CPP_DECL JSC__JSValue JSC__JSValue__symbolFor(JSC__JSGlobalObject* arg0, ZigString* arg1); CPP_DECL bool JSC__JSValue__symbolKeyFor(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, ZigString* arg2); @@ -522,6 +523,7 @@ CPP_DECL JSC__JSValue JSC__Exception__value(JSC__Exception* arg0); CPP_DECL void JSC__VM__clearExecutionTimeLimit(JSC__VM* arg0); CPP_DECL JSC__VM* JSC__VM__create(unsigned char HeapType0); +CPP_DECL void JSC__VM__deferGC(JSC__VM* arg0, void* arg1, void (* ArgFn2)(void* arg0)); CPP_DECL void JSC__VM__deinit(JSC__VM* arg0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__VM__deleteAllCode(JSC__VM* arg0, JSC__JSGlobalObject* arg1); CPP_DECL void JSC__VM__drainMicrotasks(JSC__VM* arg0); diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig index 90a3a46ea..0932fb5f5 100644 --- a/src/javascript/jsc/bindings/headers.zig +++ b/src/javascript/jsc/bindings/headers.zig @@ -338,6 +338,7 @@ pub extern fn JSC__JSValue__jsonStringify(JSValue0: JSC__JSValue, arg1: [*c]JSC_ pub extern fn JSC__JSValue__jsTDZValue(...) JSC__JSValue; pub extern fn JSC__JSValue__jsType(JSValue0: JSC__JSValue) u8; pub extern fn JSC__JSValue__jsUndefined(...) JSC__JSValue; +pub extern fn JSC__JSValue__parseJSON(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue; pub extern fn JSC__JSValue__putRecord(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString, arg3: [*c]ZigString, arg4: usize) void; pub extern fn JSC__JSValue__symbolFor(arg0: [*c]JSC__JSGlobalObject, arg1: [*c]ZigString) JSC__JSValue; pub extern fn JSC__JSValue__symbolKeyFor(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString) bool; @@ -360,6 +361,7 @@ pub extern fn JSC__Exception__getStackTrace(arg0: [*c]JSC__Exception, arg1: [*c] pub extern fn JSC__Exception__value(arg0: [*c]JSC__Exception) JSC__JSValue; pub extern fn JSC__VM__clearExecutionTimeLimit(arg0: [*c]JSC__VM) void; pub extern fn JSC__VM__create(HeapType0: u8) [*c]JSC__VM; +pub extern fn JSC__VM__deferGC(arg0: [*c]JSC__VM, arg1: ?*anyopaque, ArgFn2: ?fn (?*anyopaque) callconv(.C) void) void; pub extern fn JSC__VM__deinit(arg0: [*c]JSC__VM, arg1: [*c]JSC__JSGlobalObject) void; pub extern fn JSC__VM__deleteAllCode(arg0: [*c]JSC__VM, arg1: [*c]JSC__JSGlobalObject) void; pub extern fn JSC__VM__drainMicrotasks(arg0: [*c]JSC__VM) void; diff --git a/src/javascript/jsc/bindings/helpers.h b/src/javascript/jsc/bindings/helpers.h index 9e6da5d66..11d728ac5 100644 --- a/src/javascript/jsc/bindings/helpers.h +++ b/src/javascript/jsc/bindings/helpers.h @@ -111,7 +111,7 @@ static bool isTaggedExternalPtr(const unsigned char* ptr) static const WTF::String toString(ZigString str) { if (str.len == 0 || str.ptr == nullptr) { - return WTF::Stzring(); + return WTF::String(); } if (UNLIKELY(isTaggedUTF8Ptr(str.ptr))) { return WTF::String::fromUTF8(untag(str.ptr), str.len); diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index a0198ed65..60ba111de 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -102,6 +102,7 @@ pub const GlobalClasses = [_]type{ WebCore.TextDecoder.Constructor.Class, JSC.Cloudflare.HTMLRewriter.Class, + WebCore.Blob.Class, // The last item in this array becomes "process.env" Bun.EnvironmentVariables.Class, @@ -1951,16 +1952,19 @@ pub const VirtualMachine = struct { } } pub fn tick(this: *EventLoop) void { - this.tickConcurrent(); + while (true) { + this.tickConcurrent(); + while (this.tickWithCount() > 0) {} + this.tickConcurrent(); - while (this.tickWithCount() > 0) {} + if (this.tickWithCount() == 0) break; + } } pub fn waitForTasks(this: *EventLoop) void { - this.tickConcurrent(); - + this.tick(); while (this.pending_tasks_count.load(.Monotonic) > 0) { - while (this.tickWithCount() > 0) {} + this.tick(); } } @@ -3429,7 +3433,7 @@ pub const EventListenerMixin = struct { fetch_event.* = FetchEvent{ .request_context = request_context, - .request = Request{ .request_context = request_context }, + .request = try Request.fromRequestContext(request_context), .onPromiseRejectionCtx = @as(*anyopaque, ctx), .onPromiseRejectionHandler = FetchEventRejectionHandler.onRejection, }; diff --git a/src/javascript/jsc/node/types.zig b/src/javascript/jsc/node/types.zig index 0879c11be..e4230068a 100644 --- a/src/javascript/jsc/node/types.zig +++ b/src/javascript/jsc/node/types.zig @@ -391,6 +391,7 @@ pub const ArgumentsSlice = struct { remaining: []const JSC.JSValue, arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(bun.default_allocator), all: []const JSC.JSValue, + threw: bool = false, pub fn from(arguments: []const JSC.JSValueRef) ArgumentsSlice { return init(@ptrCast([*]const JSC.JSValue, arguments.ptr)[0..arguments.len]); diff --git a/src/javascript/jsc/test/jest.zig b/src/javascript/jsc/test/jest.zig index e2821518e..11518a581 100644 --- a/src/javascript/jsc/test/jest.zig +++ b/src/javascript/jsc/test/jest.zig @@ -685,17 +685,20 @@ pub const TestScope = struct { this.promise = null; } - while (this.promise.?.status(vm.global.vm()) == JSC.JSPromise.Status.Pending) { + var status = JSC.JSPromise.Status.Pending; + var vm_ptr = vm.global.vm(); + while (this.promise != null and status == JSC.JSPromise.Status.Pending) : (status = this.promise.?.status(vm_ptr)) { vm.tick(); } - switch (this.promise.?.status(vm.global.vm())) { + switch (status) { .Rejected => { vm.defaultErrorHandler(this.promise.?.result(vm.global.vm()), null); return .{ .fail = this.counter.actual }; }, else => { - // don't care about the result - _ = this.promise.?.result(vm.global.vm()); + if (this.promise != null) + // don't care about the result + _ = this.promise.?.result(vm.global.vm()); }, } } @@ -849,8 +852,11 @@ pub const DescribeScope = struct { var i: TestRunner.Test.ID = 0; while (i < end) { + // the test array could resize in the middle of this loop this.current_test_id = i; - const result = TestScope.run(&tests[i]); + var test_ = tests[i]; + const result = TestScope.run(&test_); + tests[i] = test_; // invalidate it this.current_test_id = std.math.maxInt(TestRunner.Test.ID); diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index f05da9af7..9103c725a 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -40,7 +40,7 @@ const VirtualMachine = @import("../javascript.zig").VirtualMachine; const Task = @import("../javascript.zig").Task; const JSPrinter = @import("../../../js_printer.zig"); const picohttp = @import("picohttp"); - +const StringJoiner = @import("../../../string_joiner.zig"); pub const Response = struct { pub const Class = NewClass( Response, @@ -48,15 +48,19 @@ pub const Response = struct { .{ .@"constructor" = constructor, .@"text" = .{ - .rfn = getText, + .rfn = Response.getText, .ts = d.ts{}, }, .@"json" = .{ - .rfn = getJson, + .rfn = Response.getJSON, .ts = d.ts{}, }, .@"arrayBuffer" = .{ - .rfn = getArrayBuffer, + .rfn = Response.getArrayBuffer, + .ts = d.ts{}, + }, + .@"blob" = .{ + .rfn = Response.getBlob, .ts = d.ts{}, }, @@ -87,6 +91,10 @@ pub const Response = struct { .@"get" = getHeaders, .ro = true, }, + .@"bodyUsed" = .{ + .@"get" = getBodyUsed, + .ro = true, + }, }, ); @@ -101,7 +109,7 @@ pub const Response = struct { pub fn writeFormat(this: *const Response, formatter: *JSC.Formatter, writer: anytype, comptime enable_ansi_colors: bool) !void { const Writer = @TypeOf(writer); try formatter.writeIndent(Writer, writer); - try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len)}); + try writer.print("Response ({}) {{\n", .{bun.fmt.size(this.body.len())}); { formatter.indent += 1; defer formatter.indent -|= 1; @@ -154,6 +162,16 @@ pub const Response = struct { return ZigString.init(this.url).withEncoding().toValueGC(ctx.ptr()).asObjectRef(); } + pub fn getBodyUsed( + this: *Response, + _: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSStringRef, + _: js.ExceptionRef, + ) js.JSValueRef { + return JSC.JSValue.jsBoolean(this.body.value == .Used).asRef(); + } + pub fn getStatusText( this: *Response, ctx: js.JSContextRef, @@ -219,194 +237,7 @@ pub const Response = struct { return new_response; } - pub fn getText( - this: *Response, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - _: js.ExceptionRef, - ) js.JSValueRef { - - // https://developer.mozilla.org/en-US/docs/Web/API/Response/text - defer this.body.value = .Empty; - return JSPromise.resolvedPromiseValue( - ctx.ptr(), - (brk: { - switch (this.body.value) { - .Unconsumed => { - if (this.body.len > 0) { - if (this.body.ptr) |_ptr| { - var zig_string = ZigString.init(_ptr[0..this.body.len]); - zig_string.detectEncoding(); - if (zig_string.is16Bit()) { - var value = zig_string.to16BitValue(ctx.ptr()); - this.body.ptr_allocator.?.free(_ptr[0..this.body.len]); - this.body.ptr_allocator = null; - this.body.ptr = null; - break :brk value; - } - - break :brk zig_string.toValue(ctx.ptr()); - } - } - - break :brk ZigString.init("").toValue(ctx.ptr()); - }, - .Empty => { - break :brk ZigString.init("").toValue(ctx.ptr()); - }, - .String => |str| { - var zig_string = ZigString.init(str); - - zig_string.detectEncoding(); - if (zig_string.is16Bit()) { - var value = zig_string.to16BitValue(ctx.ptr()); - if (this.body.ptr_allocator) |allocator| this.body.deinit(allocator); - break :brk value; - } - - break :brk zig_string.toValue(ctx.ptr()); - }, - .ArrayBuffer => |buffer| { - break :brk ZigString.init(buffer.ptr[buffer.offset..buffer.byte_len]).toValue(ctx.ptr()); - }, - else => unreachable, - } - }), - ).asRef(); - } - - var temp_error_buffer: [4096]u8 = undefined; - var error_arg_list: [1]js.JSObjectRef = undefined; - pub fn getJson( - this: *Response, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSValueRef { - var zig_string = ZigString.init(""); - var deallocate = false; - defer { - if (deallocate) { - if (this.body.value == .Unconsumed) { - this.body.ptr_allocator.?.free(this.body.ptr.?[0..this.body.len]); - this.body.ptr_allocator = null; - this.body.ptr = null; - this.body.len = 0; - } - } - - this.body.value = .Empty; - } - - var json_value = (js.JSValueMakeFromJSONString( - ctx, - brk: { - switch (this.body.value) { - .Unconsumed => { - if (this.body.ptr) |_ptr| { - zig_string = ZigString.init(_ptr[0..this.body.len]); - deallocate = true; - - break :brk zig_string.toJSStringRef(); - } - - break :brk zig_string.toJSStringRef(); - }, - .Empty => { - break :brk zig_string.toJSStringRef(); - }, - .String => |str| { - zig_string = ZigString.init(str); - break :brk zig_string.toJSStringRef(); - }, - .ArrayBuffer => |buffer| { - zig_string = ZigString.init(buffer.ptr[buffer.offset..buffer.byte_len]); - break :brk zig_string.toJSStringRef(); - }, - else => unreachable, - } - }, - ) orelse { - var out = std.fmt.bufPrint(&temp_error_buffer, "Invalid JSON\n\n \"{s}\"", .{zig_string.slice()[0..std.math.min(zig_string.len, 4000)]}) catch unreachable; - error_arg_list[0] = ZigString.init(out).toValueGC(ctx.ptr()).asRef(); - return JSPromise.rejectedPromiseValue( - ctx.ptr(), - JSValue.fromRef( - js.JSObjectMakeError( - ctx, - 1, - &error_arg_list, - exception, - ), - ), - ).asRef(); - }); - - return JSPromise.resolvedPromiseValue( - ctx.ptr(), - JSValue.fromRef(json_value), - ).asRef(); - } - pub fn getArrayBuffer( - this: *Response, - ctx: js.JSContextRef, - _: js.JSObjectRef, - _: js.JSObjectRef, - _: []const js.JSValueRef, - exception: js.ExceptionRef, - ) js.JSValueRef { - defer this.body.value = .Empty; - return JSPromise.resolvedPromiseValue( - ctx.ptr(), - JSValue.fromRef( - (brk: { - switch (this.body.value) { - .Unconsumed => { - if (this.body.ptr) |_ptr| { - break :brk JSC.MarkedArrayBuffer.fromBytes(_ptr[0..this.body.len], default_allocator, .ArrayBuffer).toJSObjectRef(ctx, exception); - } - break :brk js.JSObjectMakeTypedArray( - ctx, - js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer, - 0, - exception, - ); - }, - .Empty => { - break :brk js.JSObjectMakeTypedArray(ctx, js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer, 0, exception); - }, - .String => |str| { - break :brk js.JSObjectMakeTypedArrayWithBytesNoCopy( - ctx, - js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer, - @intToPtr([*]u8, @ptrToInt(str.ptr)), - str.len, - null, - null, - exception, - ); - }, - .ArrayBuffer => |buffer| { - break :brk js.JSObjectMakeTypedArrayWithBytesNoCopy( - ctx, - js.JSTypedArrayType.kJSTypedArrayTypeArrayBuffer, - buffer.ptr, - buffer.byte_len, - null, - null, - exception, - ); - }, - else => unreachable, - } - }), - ), - ).asRef(); - } + pub usingnamespace BlobInterface(@This()); pub fn getStatus( this: *Response, @@ -437,7 +268,7 @@ pub const Response = struct { allocator.destroy(this); } - pub fn mimeType(response: *const Response, request_ctx: *const RequestContext) string { + pub fn mimeType(response: *const Response, request_ctx_: ?*const RequestContext) string { if (response.body.init.headers) |headers| { // Remember, we always lowercase it // hopefully doesn't matter here tho @@ -446,25 +277,21 @@ pub const Response = struct { } } - if (request_ctx.url.extname.len > 0) { - return MimeType.byExtension(request_ctx.url.extname).value; + if (request_ctx_) |request_ctx| { + if (request_ctx.url.extname.len > 0) { + return MimeType.byExtension(request_ctx.url.extname).value; + } } switch (response.body.value) { - .Empty => { - return "text/plain"; - }, - .String => |body| { - // poor man's mimetype sniffing - if (body.len > 0 and (body[0] == '{' or body[0] == '[')) { - return MimeType.json.value; + .Blob => |blob| { + if (blob.content_type.len > 0) { + return blob.content_type; } - return MimeType.html.value; - }, - else => { - return "application/octet-stream"; + return MimeType.other.value; }, + .Used, .Locked, .Empty => return MimeType.other.value, } } @@ -493,10 +320,6 @@ pub const Response = struct { unreachable; }; - // if (exception != null) { - // return null; - // } - var response = getAllocator(ctx).create(Response) catch unreachable; response.* = Response{ .body = body, @@ -722,11 +545,8 @@ pub const Fetch = struct { .status_code = @truncate(u16, http_response.status_code), }, .value = .{ - .Unconsumed = 0, + .Blob = Blob.init(duped, allocator, this.global_this), }, - .ptr = duped.ptr, - .len = duped.len, - .ptr_allocator = allocator, }, }; return JSValue.fromRef(Response.Class.make(@ptrCast(js.JSContextRef, this.global_this), response)); @@ -805,92 +625,78 @@ pub const Fetch = struct { return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); } - if (!js.JSValueIsString(ctx, arguments[0])) { - const fetch_error = fetch_type_error_strings.get(js.JSValueGetType(ctx, arguments[0])); - return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); - } + var headers: ?Headers = null; + var body: MutableString = MutableString.initEmpty(bun.default_allocator); + var method = Method.GET; + var args = JSC.Node.ArgumentsSlice.from(arguments); + var url: ZigURL = undefined; + var first_arg = args.nextEat().?; + if (first_arg.isString()) { + var url_zig_str = ZigString.init(""); + JSValue.fromRef(arguments[0]).toZigString(&url_zig_str, globalThis); + var url_str = url_zig_str.slice(); + + if (url_str.len == 0) { + const fetch_error = fetch_error_blank_url; + return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); + } - var url_zig_str = ZigString.init(""); - JSValue.fromRef(arguments[0]).toZigString(&url_zig_str, globalThis); - var url_str = url_zig_str.slice(); + if (url_str[0] == '/') { + url_str = strings.append(getAllocator(ctx), VirtualMachine.vm.bundler.options.origin.origin, url_str) catch unreachable; + } else { + url_str = getAllocator(ctx).dupe(u8, url_str) catch unreachable; + } - if (url_str.len == 0) { - const fetch_error = fetch_error_blank_url; - return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); - } + NetworkThread.init() catch @panic("Failed to start network thread"); + url = ZigURL.parse(url_str); + + if (arguments.len >= 2 and js.JSValueIsObject(ctx, arguments[1])) { + var options = JSValue.fromRef(arguments[1]); + if (options.get(ctx.ptr(), "method")) |method_| { + var slice_ = method_.toSlice(ctx.ptr(), getAllocator(ctx)); + defer slice_.deinit(); + method = Method.which(slice_.slice()) orelse .GET; + } + + if (options.get(ctx.ptr(), "headers")) |headers_| { + var headers2: Headers = undefined; + if (headers_.as(Headers)) |headers__| { + headers__.clone(&headers2) catch unreachable; + headers = headers2; + } else if (Headers.JS.headersInit(ctx, headers_.asObjectRef()) catch null) |headers__| { + headers__.clone(&headers2) catch unreachable; + headers = headers2; + } + } - if (url_str[0] == '/') { - url_str = strings.append(getAllocator(ctx), VirtualMachine.vm.bundler.options.origin.origin, url_str) catch unreachable; + if (options.get(ctx.ptr(), "body")) |body__| { + if (Blob.fromJS(ctx.ptr(), body__)) |new_blob| { + body = MutableString{ .list = new_blob.asArrayList(), .allocator = bun.default_allocator }; + } else |_| {} + } + } + } else if (Request.Class.loaded and first_arg.as(Request) != null) { + var request = first_arg.as(Request).?; + url = ZigURL.parse(request.url.slice()); + method = request.method; + if (request.headers) |head| { + headers = head.*; + } + var blob = request.body.use(); + body = MutableString{ + .list = blob.asArrayList(), + .allocator = blob.allocator orelse bun.default_allocator, + }; } else { - url_str = getAllocator(ctx).dupe(u8, url_str) catch unreachable; + const fetch_error = fetch_type_error_strings.get(js.JSValueGetType(ctx, arguments[0])); + return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); } - NetworkThread.init() catch @panic("Failed to start network thread"); - const url = ZigURL.parse(url_str); - if (url.origin.len > 0 and strings.eql(url.origin, VirtualMachine.vm.bundler.options.origin.origin)) { const fetch_error = fetch_error_cant_fetch_same_origin; return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef(); } - var headers: ?Headers = null; - var body: string = ""; - var method = Method.GET; - - if (arguments.len >= 2 and js.JSValueIsObject(ctx, arguments[1])) { - var array = js.JSObjectCopyPropertyNames(ctx, arguments[1]); - defer js.JSPropertyNameArrayRelease(array); - const count = js.JSPropertyNameArrayGetCount(array); - var i: usize = 0; - while (i < count) : (i += 1) { - var property_name_ref = js.JSPropertyNameArrayGetNameAtIndex(array, i); - switch (js.JSStringGetLength(property_name_ref)) { - "headers".len => { - if (js.JSStringIsEqualToUTF8CString(property_name_ref, "headers")) { - if (js.JSObjectGetProperty(ctx, arguments[1], property_name_ref, null)) |value| { - if (GetJSPrivateData(Headers, value)) |headers_ptr| { - headers = headers_ptr.*; - } else if (Headers.JS.headersInit(ctx, value) catch null) |headers_| { - headers = headers_; - } - } - } - }, - "body".len => { - if (js.JSStringIsEqualToUTF8CString(property_name_ref, "body")) { - if (js.JSObjectGetProperty(ctx, arguments[1], property_name_ref, null)) |value| { - var body_ = Body.extractBody(ctx, value, false, null, exception); - if (exception.* != null) return js.JSValueMakeNull(ctx); - switch (body_.value) { - .ArrayBuffer => |arraybuffer| { - body = arraybuffer.ptr[0..arraybuffer.byte_len]; - }, - .String => |str| { - body = str; - }, - else => {}, - } - } - } - }, - "method".len => { - if (js.JSStringIsEqualToUTF8CString(property_name_ref, "method")) { - if (js.JSObjectGetProperty(ctx, arguments[1], property_name_ref, null)) |value| { - var string_ref = js.JSValueToStringCopy(ctx, value, exception); - - if (exception.* != null) return js.JSValueMakeNull(ctx); - defer js.JSStringRelease(string_ref); - var method_name_buf: [16]u8 = undefined; - var method_name = method_name_buf[0..js.JSStringGetUTF8CString(string_ref, &method_name_buf, method_name_buf.len)]; - method = Method.which(method_name) orelse method; - } - } - }, - else => {}, - } - } - } - var header_entries: Headers.Entries = .{}; var header_buf: string = ""; @@ -904,6 +710,13 @@ pub const Fetch = struct { js.JSValueProtect(ctx, resolve); js.JSValueProtect(ctx, reject); + var request_body: ?*MutableString = null; + if (body.list.items.len > 0) { + var mutable = bun.default_allocator.create(MutableString) catch unreachable; + mutable.* = body; + request_body = mutable; + } + // var resolve = FetchTasklet.FetchResolver.Class.make(ctx: js.JSContextRef, ptr: *ZigType) var queued = FetchTasklet.queue( default_allocator, @@ -912,10 +725,10 @@ pub const Fetch = struct { url, header_entries, header_buf, - null, + request_body, std.time.ns_per_hour, ) catch unreachable; - queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.init(&queued.data.context).ptr()); + queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.from(&queued.data.context).ptr()); js.JSValueProtect(ctx, queued.data.this_object); var promise = js.JSObjectMakeDeferredPromise(ctx, &resolve, &reject, exception); @@ -1326,7 +1139,7 @@ pub const Headers = struct { headers.entries.items(.value)[header_i] = Api.StringPointer{ .offset = headers.used, .length = end - 1 }; headers.used += end; // Can we get away with just overwriting in-place? - } else if (existing_value.length < value.len) { + } else if (existing_value.length > value.len) { std.mem.copy(u8, headers.asStr(existing_value), value); headers.entries.items(.value)[header_i].length = @truncate(u32, value.len); headers.asStr(headers.entries.items(.value)[header_i]).ptr[value.len] = 0; @@ -1523,7 +1336,7 @@ pub const Headers = struct { }) catch unreachable; } - pub fn clone(this: *Headers, to: *Headers) !void { + pub fn clone(this: *const Headers, to: *Headers) !void { to.* = Headers{ .entries = try this.entries.clone(this.allocator), .buf = try @TypeOf(this.buf).initCapacity(this.allocator, this.buf.items.len), @@ -1536,50 +1349,694 @@ pub const Headers = struct { } }; -// https://developer.mozilla.org/en-US/docs/Web/API/Body -pub const Body = struct { - init: Init, - value: Value, +pub const Blob = struct { + size: usize = 0, ptr: ?[*]u8 = null, - len: usize = 0, + cap: usize = 0, ptr_allocator: ?std.mem.Allocator = null, + allocator: ?std.mem.Allocator = null, - pub fn slice(this: *const Body) []const u8 { - return switch (this.value) { - .String => this.value.String, - .ArrayBuffer => this.value.ArrayBuffer.slice(), - else => "", + content_type: string = "", + content_type_allocated: bool = false, + detached: bool = false, + + globalThis: *JSGlobalObject, + + pub fn asArrayList(this: *const Blob) std.ArrayListUnmanaged(u8) { + if (this.ptr == null or this.size == 0) + return .{}; + + return .{ + .items = this.ptr.?[0..this.size], + .capacity = this.cap, }; } - pub fn clone(this: Body, allocator: std.mem.Allocator) Body { - var value: Value = .{ .Empty = 0 }; - var ptr: ?[*]u8 = null; - var len: usize = 0; - switch (this.value) { - .ArrayBuffer => |buffer| { - value = .{ - .ArrayBuffer = ArrayBuffer.fromBytes(allocator.dupe(u8, buffer.slice()) catch unreachable, buffer.typed_array_type), + pub const Class = NewClass( + Blob, + .{ .name = "Blob" }, + .{ + .constructor = constructor, + .finalize = finalize, + .text = .{ + .rfn = getText, + }, + .json = .{ + .rfn = getJSON, + }, + .arrayBuffer = .{ + .rfn = getArrayBuffer, + }, + .slice = .{ + .rfn = getSlice, + }, + }, + .{ + .@"type" = .{ + .get = getType, + .set = setType, + }, + .@"size" = .{ + .get = getSize, + .ro = true, + }, + }, + ); + + fn promisified( + value: JSC.JSValue, + global: *JSGlobalObject, + ) JSC.JSValue { + return JSC.JSPromise.resolvedPromiseValue(global, value); + } + + pub fn getText( + this: *Blob, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) JSC.C.JSObjectRef { + return promisified(this.toString(ctx.ptr(), .clone), ctx.ptr()).asObjectRef(); + } + + pub fn getTextTransfer( + this: *Blob, + ctx: js.JSContextRef, + ) JSC.C.JSObjectRef { + return promisified(this.toString(ctx.ptr(), .transfer), ctx.ptr()).asObjectRef(); + } + + pub fn getJSON( + this: *Blob, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) JSC.C.JSObjectRef { + return promisified(this.toJSON(ctx.ptr()), ctx.ptr()).asObjectRef(); + } + + pub fn getArrayBufferTransfer( + this: *Blob, + ctx: js.JSContextRef, + ) JSC.C.JSObjectRef { + return promisified(this.toArrayBuffer(ctx.ptr(), .transfer), ctx.ptr()).asObjectRef(); + } + + pub fn getArrayBuffer( + this: *Blob, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) JSC.C.JSObjectRef { + return promisified(this.toArrayBuffer(ctx.ptr(), .clone), ctx.ptr()).asObjectRef(); + } + + /// https://w3c.github.io/FileAPI/#slice-method-algo + /// The slice() method returns a new Blob object with bytes ranging from the + /// optional start parameter up to but not including the optional end + /// parameter, and with a type attribute that is the value of the optional + /// contentType parameter. It must act as follows: + pub fn getSlice( + this: *Blob, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + args: []const js.JSValueRef, + exception: js.ExceptionRef, + ) JSC.C.JSObjectRef { + if (this.size == 0) { + return constructor(ctx, null, &[_]js.JSValueRef{}, exception); + } + // If the optional start parameter is not used as a parameter when making this call, let relativeStart be 0. + var relativeStart: i32 = 0; + + // If the optional end parameter is not used as a parameter when making this call, let relativeEnd be size. + var relativeEnd: i32 = @intCast(i32, this.size); + + var args_iter = JSC.Node.ArgumentsSlice.from(args); + if (args_iter.nextEat()) |start_| { + const start = start_.toInt32(); + if (start < 0) { + // If the optional start parameter is negative, let relativeStart be start + size. + relativeStart = @intCast(i32, @maximum(start + @intCast(i32, this.size), 0)); + } else { + // Otherwise, let relativeStart be start. + relativeStart = @minimum(@intCast(i32, start), @intCast(i32, this.size)); + } + } + + if (args_iter.nextEat()) |end_| { + const end = end_.toInt32(); + // If end is negative, let relativeEnd be max((size + end), 0). + if (end < 0) { + // If the optional start parameter is negative, let relativeStart be start + size. + relativeEnd = @intCast(i32, @maximum(end + @intCast(i32, this.size), 0)); + } else { + // Otherwise, let relativeStart be start. + relativeEnd = @minimum(@intCast(i32, end), @intCast(i32, this.size)); + } + } + + var content_type: string = ""; + if (args_iter.nextEat()) |content_type_| { + if (content_type_.isString()) { + var zig_str = content_type_.getZigString(ctx.ptr()); + if (!zig_str.is16Bit()) { + var slice = zig_str.trimmedSlice(); + var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable; + content_type = strings.copyLowercase(slice, content_type_buf); + } + } + } + + const len = @intCast(u32, @maximum(relativeEnd - relativeStart, 0)); + + var blob = Blob.init( + getAllocator(ctx).dupe(u8, this.sharedView()[@intCast(usize, relativeStart)..][0..len]) catch unreachable, + getAllocator(ctx), + ctx.ptr(), + ); + blob.content_type = content_type; + blob.content_type_allocated = content_type.len > 0; + + var blob_ = getAllocator(ctx).create(Blob) catch unreachable; + blob_.* = blob; + blob_.allocator = getAllocator(ctx); + return Blob.Class.make(ctx, blob_); + } + + pub fn getType( + this: *Blob, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSStringRef, + _: js.ExceptionRef, + ) js.JSValueRef { + return ZigString.init(this.content_type).toValue(ctx.ptr()).asObjectRef(); + } + + pub fn setType( + this: *Blob, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSStringRef, + value: js.JSValueRef, + _: js.ExceptionRef, + ) bool { + var zig_str = JSValue.fromRef(value).getZigString(ctx.ptr()); + if (zig_str.is16Bit()) + return false; + + var slice = zig_str.trimmedSlice(); + if (strings.eql(slice, this.content_type)) + return true; + + const prev_content_type = this.content_type; + defer if (this.content_type_allocated) bun.default_allocator.free(prev_content_type); + var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable; + this.content_type = strings.copyLowercase(slice, content_type_buf); + this.content_type_allocated = true; + return true; + } + + pub fn getSize( + this: *Blob, + _: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSStringRef, + _: js.ExceptionRef, + ) js.JSValueRef { + return JSValue.jsNumber(@truncate(u32, this.size)).asRef(); + } + + pub fn constructor( + ctx: js.JSContextRef, + _: js.JSObjectRef, + args: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSObjectRef { + var blob: Blob = undefined; + switch (args.len) { + 0 => { + var empty: []u8 = &[_]u8{}; + blob = Blob.init(empty, getAllocator(ctx), ctx.ptr()); + }, + else => { + blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0])) catch { + JSC.JSError(getAllocator(ctx), "out of memory :(", .{}, ctx, exception); + return null; }; - len = buffer.len; - ptr = value.ArrayBuffer.ptr; + + if (args.len > 1) { + var options = JSValue.fromRef(args[1]); + if (options.isCell()) { + // 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(ctx.ptr(), "type")) |content_type| { + if (content_type.isString()) { + var content_type_str = content_type.getZigString(ctx.ptr()); + if (!content_type_str.is16Bit()) { + var slice = content_type_str.trimmedSlice(); + var content_type_buf = getAllocator(ctx).alloc(u8, slice.len) catch unreachable; + blob.content_type = strings.copyLowercase(slice, content_type_buf); + blob.content_type_allocated = true; + } + } + } + } + } + + if (blob.content_type.len == 0) { + blob.content_type = ""; + } }, - .String => |str| { - value = .{ - .String = allocator.dupe(u8, str) catch unreachable, + } + + var blob_ = getAllocator(ctx).create(Blob) catch unreachable; + blob_.* = blob; + blob_.allocator = getAllocator(ctx); + return Blob.Class.make(ctx, blob_); + } + + pub fn finalize(this: *Blob) void { + this.deinit(); + } + + pub fn init(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Blob { + return Blob{ + .size = bytes.len, + .ptr = bytes.ptr, + .cap = bytes.len, + .ptr_allocator = allocator, + .allocator = null, + .content_type = "", + .detached = false, + .globalThis = globalThis, + }; + } + + pub fn initEmpty(globalThis: *JSGlobalObject) Blob { + return Blob{ + .size = 0, + .ptr = undefined, + .cap = 0, + .ptr_allocator = null, + .allocator = null, + .content_type = "", + .detached = false, + .globalThis = globalThis, + }; + } + + pub fn detach(this: *Blob) void { + var blob = this.*; + if (blob.ptr_allocator) |alloc| { + alloc.free(blob.sharedView()); + this.ptr_allocator = null; + this.ptr = null; + this.size = 0; + this.cap = 0; + } + + this.detached = true; + } + + pub fn dupe(this: *const Blob, allocator: std.mem.Allocator) !Blob { + var data = try allocator.dupe(u8, this.sharedView()); + return Blob{ + .size = data.len, + .ptr = data.ptr, + .cap = data.len, + .ptr_allocator = allocator, + .allocator = allocator, + .content_type = this.content_type, + .detached = false, + .globalThis = this.globalThis, + }; + } + + pub fn deinit(this: *Blob) void { + var blob = this.*; + + if (this.allocator) |alloc| { + this.allocator = null; + alloc.destroy(this); + } + + blob.detach(); + } + + pub fn sharedView(this: *const Blob) []const u8 { + if (this.size == 0 or this.ptr == null) return ""; + return this.ptr.?[0..this.size]; + } + + pub const Lifetime = enum { + clone, + transfer, + share, + }; + + pub fn toString(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { + var view_ = this.sharedView(); + + if (view_.len == 0) + return ZigString.Empty.toValue(global); + + // TODO: use the index to make this one pass instead of two passes + var buf = view_; + if (!strings.isAllASCII(buf)) { + var external = (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) orelse &[_]u16{}; + return ZigString.toExternalU16(external.ptr, external.len, global); + } + + switch (comptime lifetime) { + .clone => { + return ZigString.init(buf).toValueGC(global); + }, + .transfer => { + this.detached = true; + this.ptr_allocator = null; + this.ptr = null; + this.size = 0; + this.cap = 0; + return ZigString.init(buf).toExternalValue(global); + }, + .share => { + return ZigString.init(buf).toValue(global); + }, + } + } + + pub fn toJSON(this: *Blob, global: *JSGlobalObject) JSValue { + var view_ = this.sharedView(); + + if (view_.len == 0) + return ZigString.Empty.toValue(global); + + // TODO: use the index to make this one pass instead of two passes + var buf = view_; + if (!strings.isAllASCII(buf)) { + var external = (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) orelse &[_]u16{}; + defer bun.default_allocator.free(external); + return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global); + } + + return ZigString.init(buf).toValue(global).parseJSON(global); + } + pub fn toArrayBuffer(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue { + var view_ = this.sharedView(); + + if (view_.len == 0) + return JSC.ArrayBuffer.fromBytes(&[_]u8{}, .ArrayBuffer).toJS(global.ref(), null); + + switch (comptime lifetime) { + .clone => { + var clone = bun.default_allocator.alloc(u8, view_.len) catch unreachable; + @memcpy(clone.ptr, view_.ptr, view_.len); + + return JSC.ArrayBuffer.fromBytes(clone, .ArrayBuffer).toJS(global.ref(), null); + }, + .share => { + return JSC.ArrayBuffer.fromBytes(view_.ptr, .ArrayBuffer).toJS(global.ref(), null); + }, + .transfer => { + this.detached = true; + this.ptr_allocator = null; + this.ptr = null; + this.size = 0; + this.cap = 0; + return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJS(global.ref(), null); + }, + } + } + + pub fn fromJS(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { + const DeferCtx = struct { + args: std.meta.ArgsTuple(@TypeOf(Blob.fromJSWithoutDeferGC)), + ret: anyerror!Blob = undefined, + + pub fn run(ctx: ?*anyopaque) callconv(.C) void { + var that = bun.cast(*@This(), ctx.?); + that.ret = @call(.{}, Blob.fromJSWithoutDeferGC, that.args); + } + }; + var ctx = DeferCtx{ + .args = .{ global, arg }, + .ret = undefined, + }; + JSC.VirtualMachine.vm.global.vm().deferGC(&ctx, DeferCtx.run); + return ctx.ret; + } + + fn fromJSWithoutDeferGC(global: *JSGlobalObject, arg: JSValue) anyerror!Blob { + var current = arg; + if (current.isUndefinedOrNull()) { + return Blob{ .globalThis = global }; + } + + // Fast path: one item, we don't need to join + switch (current.jsTypeLoose()) { + .Cell, + .NumberObject, + JSC.JSValue.JSType.String, + JSC.JSValue.JSType.StringObject, + JSC.JSValue.JSType.DerivedStringObject, + => { + var sliced = current.toSlice(global, bun.default_allocator); + if (!sliced.allocated) { + sliced.ptr = @ptrCast([*]const u8, (try bun.default_allocator.dupe(u8, sliced.slice())).ptr); + sliced.allocated = true; + } + + return Blob{ + .size = sliced.len, + .ptr = bun.constStrToU8(sliced.ptr[0..sliced.len]).ptr, + .cap = sliced.len, + .ptr_allocator = bun.default_allocator, + .allocator = null, + .globalThis = global, }; - len = str.len; - ptr = bun.constStrToU8(value.String).ptr; }, - else => {}, + + JSC.JSValue.JSType.ArrayBuffer, + JSC.JSValue.JSType.Int8Array, + JSC.JSValue.JSType.Uint8Array, + JSC.JSValue.JSType.Uint8ClampedArray, + JSC.JSValue.JSType.Int16Array, + JSC.JSValue.JSType.Uint16Array, + JSC.JSValue.JSType.Int32Array, + JSC.JSValue.JSType.Uint32Array, + JSC.JSValue.JSType.Float32Array, + JSC.JSValue.JSType.Float64Array, + JSC.JSValue.JSType.BigInt64Array, + JSC.JSValue.JSType.BigUint64Array, + JSC.JSValue.JSType.DataView, + => { + var buf = try bun.default_allocator.dupe(u8, current.asArrayBuffer(global).?.slice()); + return Blob{ + .size = buf.len, + .ptr = buf.ptr, + .cap = buf.len, + .ptr_allocator = bun.default_allocator, + .allocator = null, + .globalThis = global, + }; + }, + + else => { + if (JSC.C.JSObjectGetPrivate(current.asObjectRef())) |priv| { + var data = JSC.JSPrivateDataPtr.from(priv); + switch (data.tag()) { + .Blob => { + var blob: *Blob = data.as(Blob); + return try blob.dupe(bun.default_allocator); + }, + + else => return Blob.initEmpty(global), + } + } + }, } + var stack_allocator = std.heap.stackFallback(1024, bun.default_allocator); + var stack_mem_all = stack_allocator.get(); + var stack: std.ArrayList(JSValue) = std.ArrayList(JSValue).init(stack_mem_all); + var joiner = StringJoiner{ .use_pool = false, .node_allocator = stack_mem_all }; + + defer if (stack_allocator.fixed_buffer_allocator.end_index >= 1024) stack.deinit(); + + while (true) { + switch (current.jsTypeLoose()) { + .NumberObject, + JSC.JSValue.JSType.String, + JSC.JSValue.JSType.StringObject, + JSC.JSValue.JSType.DerivedStringObject, + => { + var sliced = current.toSlice(global, bun.default_allocator); + joiner.append( + sliced.slice(), + 0, + if (sliced.allocated) sliced.allocator else null, + ); + }, + + .Array, .DerivedArray => { + var iter = JSC.JSArrayIterator.init(current, global); + try stack.ensureUnusedCapacity(iter.len); + var any_arrays = false; + while (iter.next()) |item| { + if (item.isUndefinedOrNull()) continue; + + // When it's a string or ArrayBuffer inside an array, we can avoid the extra push/pop + // we only really want this for nested arrays + // However, we must preserve the order + // That means if there are any arrays + // we have to restart the loop + if (!any_arrays) { + switch (item.jsTypeLoose()) { + .NumberObject, + .Cell, + JSC.JSValue.JSType.String, + JSC.JSValue.JSType.StringObject, + JSC.JSValue.JSType.DerivedStringObject, + => { + var sliced = item.toSlice(global, bun.default_allocator); + joiner.append( + sliced.slice(), + 0, + if (sliced.allocated) sliced.allocator else null, + ); + continue; + }, + JSC.JSValue.JSType.ArrayBuffer, + JSC.JSValue.JSType.Int8Array, + JSC.JSValue.JSType.Uint8Array, + JSC.JSValue.JSType.Uint8ClampedArray, + JSC.JSValue.JSType.Int16Array, + JSC.JSValue.JSType.Uint16Array, + JSC.JSValue.JSType.Int32Array, + JSC.JSValue.JSType.Uint32Array, + JSC.JSValue.JSType.Float32Array, + JSC.JSValue.JSType.Float64Array, + JSC.JSValue.JSType.BigInt64Array, + JSC.JSValue.JSType.BigUint64Array, + JSC.JSValue.JSType.DataView, + => { + var buf = item.asArrayBuffer(global).?; + joiner.append(buf.slice(), 0, null); + continue; + }, + .Array, .DerivedArray => { + any_arrays = true; + break; + }, + else => { + if (JSC.C.JSObjectGetPrivate(item.asObjectRef())) |priv| { + var data = JSC.JSPrivateDataPtr.from(priv); + switch (data.tag()) { + .Blob => { + var blob: *Blob = data.as(Blob); + joiner.append(blob.sharedView(), 0, null); + continue; + }, + else => {}, + } + } + }, + } + } + + stack.appendAssumeCapacity(item); + } + }, + + JSC.JSValue.JSType.ArrayBuffer, + JSC.JSValue.JSType.Int8Array, + JSC.JSValue.JSType.Uint8Array, + JSC.JSValue.JSType.Uint8ClampedArray, + JSC.JSValue.JSType.Int16Array, + JSC.JSValue.JSType.Uint16Array, + JSC.JSValue.JSType.Int32Array, + JSC.JSValue.JSType.Uint32Array, + JSC.JSValue.JSType.Float32Array, + JSC.JSValue.JSType.Float64Array, + JSC.JSValue.JSType.BigInt64Array, + JSC.JSValue.JSType.BigUint64Array, + JSC.JSValue.JSType.DataView, + => { + var buf = current.asArrayBuffer(global).?; + joiner.append(buf.slice(), 0, null); + }, + + else => { + outer: { + if (JSC.C.JSObjectGetPrivate(current.asObjectRef())) |priv| { + var data = JSC.JSPrivateDataPtr.from(priv); + switch (data.tag()) { + .Blob => { + var blob: *Blob = data.as(Blob); + joiner.append(blob.sharedView(), 0, null); + break :outer; + }, + else => {}, + } + } + + var sliced = current.toSlice(global, bun.default_allocator); + joiner.append( + sliced.slice(), + 0, + if (sliced.allocated) sliced.allocator else null, + ); + } + }, + } + + current = stack.popOrNull() orelse break; + } + + var joined = try joiner.done(bun.default_allocator); + return Blob{ + .ptr_allocator = bun.default_allocator, + .allocator = null, + .size = joined.len, + .ptr = joined.ptr, + .cap = joined.len, + .globalThis = global, + }; + } +}; + +// https://developer.mozilla.org/en-US/docs/Web/API/Body +pub const Body = struct { + init: Init = Init{ .headers = null, .status_code = 200 }, + value: Value = Value.empty, + + pub inline fn len(this: *const Body) usize { + return this.slice().len; + } + + pub fn slice(this: *const Body) []const u8 { + return this.value.slice(); + } + + pub fn use(this: *Body) Blob { + return this.value.use(); + } + + pub fn clone(this: Body, allocator: std.mem.Allocator) Body { return Body{ .init = this.init.clone(allocator), - .value = value, - .ptr_allocator = if (len > 0) allocator else null, - .ptr = ptr, - .len = len, + .value = this.value.clone(allocator) catch Value.empty, }; } @@ -1588,7 +2045,7 @@ pub const Body = struct { try formatter.writeIndent(Writer, writer); try writer.writeAll("bodyUsed: "); - formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(!(this.value == .Unconsumed or this.value == .Empty)), .BooleanObject, enable_ansi_colors); + formatter.printAs(.Boolean, Writer, writer, JSC.JSValue.jsBoolean(this.value == .Used), .BooleanObject, enable_ansi_colors); try formatter.printComma(Writer, writer, enable_ansi_colors); try writer.writeAll("\n"); @@ -1604,25 +2061,18 @@ pub const Body = struct { formatter.printAs(.Double, Writer, writer, JSC.JSValue.jsNumber(this.init.status_code), .NumberObject, enable_ansi_colors); } - pub fn deinit(this: *Body, allocator: std.mem.Allocator) void { - this.ptr_allocator = null; + pub fn deinit(this: *Body, _: std.mem.Allocator) void { if (this.init.headers) |*headers| { headers.deinit(); } - switch (this.value) { - .ArrayBuffer => {}, - .String => |str| { - allocator.free(str); - }, - .Empty => {}, - else => {}, - } + this.value.deinit(); } pub const Init = struct { headers: ?Headers, status_code: u16, + method: Method = Method.GET, pub fn clone(this: Init, allocator: std.mem.Allocator) Init { var that = this; @@ -1642,6 +2092,7 @@ pub const Body = struct { var array = js.JSObjectCopyPropertyNames(ctx, init_ref); defer js.JSPropertyNameArrayRelease(array); const count = js.JSPropertyNameArrayGetCount(array); + var i: usize = 0; while (i < count) : (i += 1) { var property_name_ref = js.JSPropertyNameArrayGetNameAtIndex(array, i); @@ -1668,6 +2119,13 @@ pub const Body = struct { result.status_code = @truncate(u16, @floatToInt(u64, number)); } }, + "method".len => { + if (js.JSStringIsEqualToUTF8CString(property_name_ref, "method")) { + result.method = Method.which( + JSC.JSValue.fromRef(init_ref).get(ctx.ptr(), "method").?.getZigString(ctx.ptr()).slice(), + ) orelse Method.GET; + } + }, else => {}, } } @@ -1681,50 +2139,83 @@ pub const Body = struct { promise: ?JSValue = null, global: *JSGlobalObject, task: ?*anyopaque = null, + deinit: bool = false, }; pub const Value = union(Tag) { - ArrayBuffer: ArrayBuffer, - String: string, - Empty: u0, - Unconsumed: u0, + Blob: Blob, Locked: PendingValue, + Used: void, + Empty: void, pub const Tag = enum { - ArrayBuffer, - String, - Empty, - Unconsumed, + Blob, Locked, + Used, + Empty, }; - pub fn length(value: *const Value) usize { - switch (value.*) { - .ArrayBuffer => |buf| { - return buf.ptr[buf.offset..buf.byte_len].len; - }, - .String => |str| { - return str.len; + pub const empty = Value{ .Empty = .{} }; + pub fn slice(this: Value) []const u8 { + return switch (this) { + .Blob => this.Blob.sharedView(), + else => "", + }; + } + + pub fn use(this: *Value) Blob { + switch (this.*) { + .Blob => { + var new_blob = this.Blob; + this.* = .{ .Used = .{} }; + return new_blob; }, else => { - return 0; + return Blob.initEmpty(undefined); }, } } + + pub fn deinit(this: *Value) void { + const tag = @as(Tag, this.*); + if (tag == .Locked) { + this.Locked.deinit = true; + return; + } + + if (tag == .Blob) { + this.Blob.deinit(); + this.* = Value.empty; + } + } + + pub fn clone(this: Value, allocator: std.mem.Allocator) !Value { + if (this == .Blob) { + return Value{ .Blob = try this.Blob.dupe(allocator) }; + } + + return Value{ .Empty = .{} }; + } }; pub fn @"404"(_: js.JSContextRef) Body { - return Body{ .init = Init{ - .headers = null, - .status_code = 404, - }, .value = .{ .Empty = 0 } }; + return Body{ + .init = Init{ + .headers = null, + .status_code = 404, + }, + .value = Value.empty, + }; } pub fn @"200"(_: js.JSContextRef) Body { - return Body{ .init = Init{ - .headers = null, - .status_code = 200, - }, .value = .{ .Empty = 0 } }; + return Body{ + .init = Init{ + .headers = null, + .status_code = 200, + }, + .value = Value.empty, + }; } pub fn extract(ctx: js.JSContextRef, body_ref: js.JSObjectRef, exception: js.ExceptionRef) Body { @@ -1755,74 +2246,26 @@ pub const Body = struct { init_ref: js.JSValueRef, exception: js.ExceptionRef, ) Body { - var body = Body{ .init = Init{ .headers = null, .status_code = 200 }, .value = .{ .Empty = 0 } }; + var body = Body{ + .init = Init{ .headers = null, .status_code = 200 }, + }; const value = JSC.JSValue.fromRef(body_ref); - switch (value.jsType()) { - JSC.JSValue.JSType.String, - JSC.JSValue.JSType.StringObject, - JSC.JSValue.JSType.DerivedStringObject, - => { - var allocator = getAllocator(ctx); + var allocator = getAllocator(ctx); - if (comptime has_init) { - if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| { - if (maybeInit) |init_| { - body.init = init_; - } - } else |_| {} + if (comptime has_init) { + if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| { + if (maybeInit) |init_| { + body.init = init_; } - var zig_str = JSC.ZigString.init(""); - value.toZigString(&zig_str, ctx.ptr()); - - if (zig_str.len == 0) { - body.value = .{ .String = "" }; - return body; - } - - body.value = Value{ - .String = std.fmt.allocPrint(default_allocator, "{}", .{zig_str}) catch unreachable, - }; - body.ptr_allocator = default_allocator; - // body.ptr = body. - // body.len = body.value.String.len;str.characters8()[0..len] }; - - return body; - }, - - JSC.JSValue.JSType.ArrayBuffer, - JSC.JSValue.JSType.Int8Array, - JSC.JSValue.JSType.Uint8Array, - JSC.JSValue.JSType.Uint8ClampedArray, - JSC.JSValue.JSType.Int16Array, - JSC.JSValue.JSType.Uint16Array, - JSC.JSValue.JSType.Int32Array, - JSC.JSValue.JSType.Uint32Array, - JSC.JSValue.JSType.Float32Array, - JSC.JSValue.JSType.Float64Array, - JSC.JSValue.JSType.BigInt64Array, - JSC.JSValue.JSType.BigUint64Array, - JSC.JSValue.JSType.DataView, - => { - var allocator = getAllocator(ctx); + } else |_| {} + } - if (comptime has_init) { - if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| { - if (maybeInit) |init_| { - body.init = init_; - } - } else |_| {} - } - body.value = Value{ .ArrayBuffer = value.asArrayBuffer(ctx.ptr()).? }; - body.ptr = body.value.ArrayBuffer.ptr[body.value.ArrayBuffer.offset..body.value.ArrayBuffer.byte_len].ptr; - body.len = body.value.ArrayBuffer.ptr[body.value.ArrayBuffer.offset..body.value.ArrayBuffer.byte_len].len; + body.value = .{ + .Blob = Blob.fromJS(ctx.ptr(), value) catch { + JSC.JSError(allocator, "Out of memory", .{}, ctx, exception); return body; }, - else => {}, - } - - if (exception == null) { - JSError(getAllocator(ctx), "Body must be a string or a TypedArray (for now)", .{}, ctx, exception); - } + }; return body; } @@ -1830,9 +2273,44 @@ pub const Body = struct { // https://developer.mozilla.org/en-US/docs/Web/API/Request pub const Request = struct { - request_context: *RequestContext, - url_string_ref: js.JSStringRef = null, - headers: ?Headers = null, + url: ZigString = ZigString.Empty, + headers: ?*Headers = null, + body: Body.Value = Body.Value{ .Empty = .{} }, + method: Method = Method.GET, + + pub fn fromRequestContext(ctx: *RequestContext) !Request { + var headers_ = bun.default_allocator.create(Headers) catch unreachable; + var req = Request{ + .url = ZigString.init(std.mem.span(ctx.getFullURL())), + .body = Body.Value.empty, + .method = ctx.method, + .headers = headers_, + }; + headers_.* = Headers.fromRequestCtx(bun.default_allocator, ctx) catch unreachable; + req.url.mark(); + return req; + } + + pub fn mimeType(this: *const Request) string { + if (this.headers) |headers| { + // Remember, we always lowercase it + // hopefully doesn't matter here tho + if (headers.getHeaderIndex("content-type")) |content_type| { + return headers.asStr(headers.entries.items(.value)[content_type]); + } + } + + switch (this.body) { + .Blob => |blob| { + if (blob.content_type.len > 0) { + return blob.content_type; + } + + return MimeType.other.value; + }, + .Used, .Locked, .Empty => return MimeType.other.value, + } + } pub const Class = NewClass( Request, @@ -1840,7 +2318,17 @@ pub const Request = struct { .name = "Request", .read_only = true, }, - .{}, + .{ .finalize = finalize, .constructor = constructor, .text = .{ + .rfn = Request.getText, + }, .json = .{ + .rfn = Request.getJSON, + }, .arrayBuffer = .{ + .rfn = Request.getArrayBuffer, + }, .blob = .{ + .rfn = Request.getBlob, + }, .clone = .{ + .rfn = Request.doClone, + } }, .{ .@"cache" = .{ .@"get" = getCache, @@ -1886,6 +2374,10 @@ pub const Request = struct { .@"get" = getUrl, .@"ro" = true, }, + .@"bodyUsed" = .{ + .@"get" = getBodyUsed, + .@"ro" = true, + }, }, ); @@ -1924,10 +2416,12 @@ pub const Request = struct { _: js.ExceptionRef, ) js.JSValueRef { if (this.headers == null) { - this.headers = Headers.fromRequestCtx(getAllocator(ctx), this.request_context) catch unreachable; + var headers = getAllocator(ctx).create(Headers) catch unreachable; + headers.* = Headers.empty(getAllocator(ctx)); + this.headers = headers; } - return Headers.Class.make(ctx, &this.headers.?); + return Headers.Class.make(ctx, this.headers.?); } pub fn getIntegrity( _: *Request, @@ -1945,7 +2439,7 @@ pub const Request = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - const string_contents: string = switch (this.request_context.method) { + const string_contents: string = switch (this.method) { .GET => Properties.UTF8.GET, .HEAD => Properties.UTF8.HEAD, .PATCH => Properties.UTF8.PATCH, @@ -1955,7 +2449,7 @@ pub const Request = struct { else => "", }; - return ZigString.init(string_contents).toValueGC(ctx.ptr()).asRef(); + return ZigString.init(string_contents).toValue(ctx.ptr()).asRef(); } pub fn getMode( @@ -1965,8 +2459,13 @@ pub const Request = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - return ZigString.init(Properties.UTF8.navigate).toValueGC(ctx.ptr()).asRef(); + return ZigString.init(Properties.UTF8.navigate).toValue(ctx.ptr()).asRef(); } + + pub fn finalize(this: *Request) void { + bun.default_allocator.destroy(this); + } + pub fn getRedirect( _: *Request, ctx: js.JSContextRef, @@ -1983,11 +2482,13 @@ pub const Request = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - if (this.request_context.header("Referrer")) |referrer| { - return ZigString.init(referrer).toValueGC(ctx.ptr()).asRef(); - } else { - return ZigString.init("").toValueGC(ctx.ptr()).asRef(); + if (this.headers) |headers| { + if (headers.getHeaderIndex("referrer")) |i| { + return ZigString.init(headers.asStr(headers.entries.get(i).value)).toValueGC(ctx.ptr()).asObjectRef(); + } } + + return ZigString.init("").toValueGC(ctx.ptr()).asRef(); } pub fn getReferrerPolicy( _: *Request, @@ -2005,14 +2506,146 @@ pub const Request = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - if (this.url_string_ref == null) { - this.url_string_ref = js.JSStringCreateWithUTF8CString(this.request_context.getFullURL()); + return this.url.toValueGC(ctx.ptr()).asObjectRef(); + } + + pub fn constructor( + ctx: js.JSContextRef, + _: js.JSObjectRef, + arguments: []const js.JSValueRef, + _: js.ExceptionRef, + ) js.JSObjectRef { + var request = Request{}; + + switch (arguments.len) { + 0 => {}, + 1 => { + request.url = JSC.JSValue.fromRef(arguments[0]).getZigString(ctx.ptr()); + }, + else => { + request.url = JSC.JSValue.fromRef(arguments[0]).getZigString(ctx.ptr()); + + if (Body.Init.init(getAllocator(ctx), ctx, arguments[1]) catch null) |req_init| { + request.headers = getAllocator(ctx).create(Headers) catch unreachable; + if (req_init.headers) |headers_| { + request.headers.?.* = headers_; + } + + request.method = req_init.method; + } + + if (JSC.JSValue.fromRef(arguments[1]).get(ctx.ptr(), "body")) |body_| { + if (Blob.fromJS(ctx.ptr(), body_) catch null) |blob| { + request.body = Body.Value{ .Blob = blob }; + } + } + }, + } + + var request_ = getAllocator(ctx).create(Request) catch unreachable; + request_.* = request; + return Request.Class.make( + ctx, + request_, + ); + } + + pub fn getBodyUsed( + this: *Request, + _: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSStringRef, + _: js.ExceptionRef, + ) js.JSValueRef { + return JSC.JSValue.jsBoolean(this.body == .Used).asRef(); + } + + pub usingnamespace BlobInterface(@This()); + + pub fn doClone( + this: *Request, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) js.JSValueRef { + var cloned = this.clone(getAllocator(ctx)); + return Request.Class.make(ctx, cloned); + } + + pub fn cloneInto(this: *const Request, req: *Request, allocator: std.mem.Allocator) void { + req.* = Request{ + .body = this.body.clone(allocator) catch unreachable, + .url = ZigString.init(allocator.dupe(u8, this.url.slice()) catch unreachable), + }; + if (this.headers) |head| { + var new_headers = allocator.create(Headers) catch unreachable; + head.clone(new_headers) catch unreachable; + req.headers = new_headers; } + } - return js.JSValueMakeString(ctx, this.url_string_ref); + pub fn clone(this: *const Request, allocator: std.mem.Allocator) *Request { + var req = allocator.create(Request) catch unreachable; + this.cloneInto(req, allocator); + return req; } }; +fn BlobInterface(comptime Type: type) type { + return struct { + pub fn getText( + this: *Type, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) js.JSValueRef { + var blob = this.body.use(); + return blob.getTextTransfer(ctx); + } + + pub fn getJSON( + this: *Type, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + exception: js.ExceptionRef, + ) js.JSValueRef { + var blob = this.body.use(); + return blob.getJSON(ctx, null, null, &.{}, exception); + } + pub fn getArrayBuffer( + this: *Type, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) js.JSValueRef { + var blob = this.body.use(); + return blob.getArrayBufferTransfer(ctx); + } + + pub fn getBlob( + this: *Type, + ctx: js.JSContextRef, + _: js.JSObjectRef, + _: js.JSObjectRef, + _: []const js.JSValueRef, + _: js.ExceptionRef, + ) js.JSValueRef { + var blob = this.body.use(); + var ptr = getAllocator(ctx).create(Blob) catch unreachable; + ptr.* = blob; + return JSC.JSPromise.resolvedPromiseValue(ctx.ptr(), JSValue.fromRef(Blob.Class.make(ctx, ptr))).asObjectRef(); + } + }; +} + // https://github.com/WebKit/WebKit/blob/main/Source/WebCore/workers/service/FetchEvent.h pub const FetchEvent = struct { started_waiting_at: u64 = 0, @@ -2083,7 +2716,12 @@ pub const FetchEvent = struct { _: js.JSStringRef, _: js.ExceptionRef, ) js.JSValueRef { - return Request.Class.make(ctx, &this.request); + var req = getAllocator(ctx).create(Request) catch unreachable; + req.* = Request.fromRequestContext(this.request_context) catch unreachable; + return Request.Class.make( + ctx, + req, + ); } // https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith @@ -2114,7 +2752,7 @@ pub const FetchEvent = struct { if (this.pending_promise) |promise| { var status = promise.status(globalThis.vm()); - if (status == .Pending) { + while (status == .Pending) { VirtualMachine.vm.tick(); status = promise.status(globalThis.vm()); } @@ -2198,7 +2836,10 @@ pub const FetchEvent = struct { this.request_context.appendHeader("Content-Type", response.mimeType(this.request_context)); } - const content_length_ = content_length orelse response.body.value.length(); + var blob = response.body.value.use(); + // defer blob.deinit(); + + const content_length_ = content_length orelse blob.size; if (content_length_ == 0) { this.request_context.sendNoContent() catch return js.JSValueMakeUndefined(ctx); @@ -2206,46 +2847,19 @@ pub const FetchEvent = struct { } if (FeatureFlags.strong_etags_for_built_files) { - switch (response.body.value) { - .ArrayBuffer => |buf| { - const did_send = this.request_context.writeETag(buf.ptr[buf.offset..buf.byte_len]) catch false; - if (did_send) return js.JSValueMakeUndefined(ctx); - }, - .String => |str| { - const did_send = this.request_context.writeETag(str) catch false; - if (did_send) { - // defer getAllocator(ctx).destroy(str.ptr); - return js.JSValueMakeUndefined(ctx); - } - }, - else => unreachable, + const did_send = this.request_context.writeETag(blob.sharedView()) catch false; + if (did_send) { + // defer getAllocator(ctx).destroy(str.ptr); + return js.JSValueMakeUndefined(ctx); } } defer this.request_context.done(); - defer { - if (response.body.ptr_allocator) |alloc| { - if (response.body.ptr) |ptr| { - alloc.free(ptr[0..response.body.len]); - } - - response.body.ptr_allocator = null; - } - } this.request_context.writeStatusSlow(response.body.init.status_code) catch return js.JSValueMakeUndefined(ctx); this.request_context.prepareToSendBody(content_length_, false) catch return js.JSValueMakeUndefined(ctx); - switch (response.body.value) { - .ArrayBuffer => |buf| { - this.request_context.writeBodyBuf(buf.ptr[buf.offset..buf.byte_len]) catch return js.JSValueMakeUndefined(ctx); - }, - .String => |str| { - // defer getAllocator(ctx).destroy(str.ptr); - this.request_context.writeBodyBuf(str) catch return js.JSValueMakeUndefined(ctx); - }, - else => unreachable, - } + this.request_context.writeBodyBuf(blob.sharedView()) catch return js.JSValueMakeUndefined(ctx); return js.JSValueMakeUndefined(ctx); } diff --git a/src/js_ast.zig b/src/js_ast.zig index d203586fb..e2edca77c 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -27,6 +27,7 @@ const AutoHashMap = _hash_map.AutoHashMap; const StringHashMapUnmanaged = _hash_map.StringHashMapUnmanaged; const is_bindgen = std.meta.globalOption("bindgen", bool) orelse false; const ComptimeStringMap = bun.ComptimeStringMap; +const JSPrinter = @import("./js_printer.zig"); pub fn NewBaseStore(comptime Union: anytype, comptime count: usize) type { var max_size = 0; var max_align = 1; @@ -2193,6 +2194,57 @@ pub const Expr = struct { loc: logger.Loc, data: Data, + pub fn fromBlob( + blob: *const JSC.WebCore.Blob, + allocator: std.mem.Allocator, + mime_type_: ?HTTP.MimeType, + log: *logger.Log, + loc: logger.Loc, + ) !Expr { + var bytes = blob.sharedView(); + + const mime_type = mime_type_ orelse HTTP.MimeType.init(blob.content_type); + + if (mime_type.category == .json) { + var source = logger.Source.initPathString("fetch.json", bytes); + var out_expr = JSONParser.ParseJSONForMacro(&source, log, allocator) catch { + return error.MacroFailed; + }; + out_expr.loc = loc; + + switch (out_expr.data) { + .e_object => { + out_expr.data.e_object.was_originally_macro = true; + }, + .e_array => { + out_expr.data.e_array.was_originally_macro = true; + }, + else => {}, + } + + return out_expr; + } + + if (mime_type.category.isTextLike()) { + var output = MutableString.initEmpty(allocator); + output = try JSPrinter.quoteForJSON(bytes, output, true); + var list = output.toOwnedSlice(); + // remove the quotes + if (list.len > 0) { + list = list[1 .. list.len - 1]; + } + return Expr.init(E.String, E.String{ .utf8 = list }, loc); + } + + return Expr.init( + E.String, + E.String{ + .utf8 = try JSC.ZigString.init(bytes).toBase64DataURL(allocator), + }, + loc, + ); + } + pub inline fn initIdentifier(ref: Ref, loc: logger.Loc) Expr { return Expr{ .loc = loc, @@ -7713,98 +7765,60 @@ pub const Macro = struct { if (_entry.found_existing) { return _entry.value_ptr.*; } - - if (JSCBase.GetJSPrivateData(JSNode, value.asObjectRef())) |node| { - _entry.value_ptr.* = node.toExpr(); - node.visited = true; - node.updateSymbolsMap(Visitor, this.visitor); - return _entry.value_ptr.*; + var private_data = JSCBase.JSPrivateDataPtr.from(JSC.C.JSObjectGetPrivate(value.asObjectRef()).?); + var blob_: ?JSC.WebCore.Blob = null; + var mime_type: ?HTTP.MimeType = null; + + switch (private_data.tag()) { + .JSNode => { + var node = private_data.as(JSNode); + _entry.value_ptr.* = node.toExpr(); + node.visited = true; + node.updateSymbolsMap(Visitor, this.visitor); + return _entry.value_ptr.*; + }, + .ResolveError, .BuildError => { + this.macro.vm.defaultErrorHandler(value, null); + return error.MacroFailed; + }, + .Request => { + var req: *JSC.WebCore.Request = private_data.as(JSC.WebCore.Request); + blob_ = req.body.use(); + mime_type = HTTP.MimeType.init(req.mimeType()); + }, + .Response => { + var res: *JSC.WebCore.Response = private_data.as(JSC.WebCore.Response); + blob_ = res.body.use(); + mime_type = + HTTP.MimeType.init(res.mimeType(null)); + }, + .Blob => { + var blob = private_data.as(JSC.WebCore.Blob); + blob_ = blob.*; + blob.* = JSC.WebCore.Blob.initEmpty(blob.globalThis); + }, + else => {}, } - if (JSCBase.GetJSPrivateData(JSC.BuildError, value.asObjectRef()) != null) { - this.macro.vm.defaultErrorHandler(value, null); - return error.MacroFailed; - } + if (blob_) |*blob| { + const out_expr = Expr.fromBlob( + blob, + this.allocator, + mime_type, + this.log, + this.caller.loc, + ) catch { + blob.deinit(); + return error.MacroFailed; + }; + if (out_expr.data == .e_string) { + blob.deinit(); + } - if (JSCBase.GetJSPrivateData(JSC.ResolveError, value.asObjectRef()) != null) { - this.macro.vm.defaultErrorHandler(value, null); - return error.MacroFailed; + return out_expr; } - // alright this is insane - if (JSCBase.GetJSPrivateData(JSC.WebCore.Response, value.asObjectRef())) |response| { - switch (response.body.value) { - .Unconsumed => { - if (response.body.len > 0) { - var mime_type = HTTP.MimeType.other; - if (response.body.init.headers) |headers| { - if (headers.getHeaderIndex("content-type")) |content_type| { - mime_type = HTTP.MimeType.init(headers.asStr(headers.entries.get(content_type).value)); - } - } - - if (response.body.ptr) |_ptr| { - var zig_string = JSC.ZigString.init(_ptr[0..response.body.len]); - - if (mime_type.category == .json) { - var source = logger.Source.initPathString("fetch.json", zig_string.slice()); - var out_expr = JSONParser.ParseJSONForMacro(&source, this.log, this.allocator) catch { - return error.MacroFailed; - }; - switch (out_expr.data) { - .e_object => { - out_expr.data.e_object.was_originally_macro = true; - }, - .e_array => { - out_expr.data.e_array.was_originally_macro = true; - }, - else => {}, - } - - return out_expr; - } - - if (mime_type.category.isTextLike()) { - zig_string.detectEncoding(); - const utf8 = if (zig_string.is16Bit()) - zig_string.toSlice(this.allocator).slice() - else - zig_string.slice(); - - return Expr.init(E.String, E.String{ .utf8 = utf8 }, this.caller.loc); - } - - return Expr.init(E.String, E.String{ .utf8 = zig_string.toBase64DataURL(this.allocator) catch unreachable }, this.caller.loc); - } - } - - return Expr.init(E.String, E.String.empty, this.caller.loc); - }, - - .String => |str| { - var zig_string = JSC.ZigString.init(str); - - zig_string.detectEncoding(); - if (zig_string.is16Bit()) { - var slice = zig_string.toSlice(this.allocator); - if (response.body.ptr_allocator) |allocator| response.body.deinit(allocator); - return Expr.init(E.String, E.String{ .utf8 = slice.slice() }, this.caller.loc); - } - - return Expr.init(E.String, E.String{ .utf8 = zig_string.slice() }, this.caller.loc); - }, - .ArrayBuffer => |buffer| { - return Expr.init( - E.String, - E.String{ .utf8 = JSC.ZigString.init(buffer.slice()).toBase64DataURL(this.allocator) catch unreachable }, - this.caller.loc, - ); - }, - else => { - return Expr.init(E.String, E.String.empty, this.caller.loc); - }, - } - } + return Expr.init(E.String, E.String.empty, this.caller.loc); }, .Boolean => { diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 83b69692a..8d6032b27 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -41,7 +41,7 @@ const NodeModuleBundle = @import("../node_module_bundle.zig").NodeModuleBundle; pub fn isPackagePath(path: string) bool { // this could probably be flattened into something more optimized - return path[0] != '/' and !strings.startsWith(path, "./") and !strings.startsWith(path, "../") and !strings.eql(path, ".") and !strings.eql(path, ".."); + return path.len > 0 and path[0] != '/' and !strings.startsWith(path, "./") and !strings.startsWith(path, "../") and !strings.eql(path, ".") and !strings.eql(path, ".."); } pub const SideEffectsData = struct { diff --git a/src/string_joiner.zig b/src/string_joiner.zig index d49c32867..03d63364e 100644 --- a/src/string_joiner.zig +++ b/src/string_joiner.zig @@ -21,27 +21,35 @@ const Joinable = struct { last_byte: u8 = 0, len: usize = 0, +use_pool: bool = true, +node_allocator: std.mem.Allocator = undefined, head: ?*Joinable.Pool.Node = null, tail: ?*Joinable.Pool.Node = null, pub fn done(this: *Joiner, allocator: std.mem.Allocator) ![]u8 { - var slice = try allocator.alloc(u8, this.cap); + if (this.head == null) { + var out: []u8 = &[_]u8{}; + return out; + } + + var slice = try allocator.alloc(u8, this.len); var remaining = slice; var el_ = this.head; while (el_) |join| { - const to_join = join.data.slice[join.offset..]; + const to_join = join.data.slice[join.data.offset..]; @memcpy(remaining.ptr, to_join.ptr, to_join.len); - remaining = remaining[to_join.len..]; + remaining = remaining[@minimum(remaining.len, to_join.len)..]; var prev = join; el_ = join.next; if (prev.data.needs_deinit) { - prev.data.allocator.free(join.data.slice); + prev.data.allocator.free(prev.data.slice); prev.data = Joinable{}; } - prev.release(); + + if (this.use_pool) prev.release(); } return slice[0 .. slice.len - remaining.len]; @@ -60,12 +68,19 @@ pub fn append(this: *Joiner, slice: string, offset: u32, allocator: ?std.mem.All const data = slice[offset..]; this.len += @truncate(u32, data.len); - var new_tail = Joinable.Pool.get(default_allocator); - new_tail.data = Joinable{ - .offset = offset, - .allocator = allocator orelse undefined, - .needs_deinit = allocator != null, - .slice = slice, + var new_tail = if (this.use_pool) + Joinable.Pool.get(default_allocator) + else + (this.node_allocator.create(Joinable.Pool.Node) catch unreachable); + + new_tail.* = .{ + .allocator = default_allocator, + .data = Joinable{ + .offset = @truncate(u31, offset), + .allocator = allocator orelse undefined, + .needs_deinit = allocator != null, + .slice = slice, + }, }; var tail = this.tail orelse { |