From ba947b8c7661e9b518462351f40f459bccafc859 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 17 Apr 2026 14:26:42 +0800 Subject: [PATCH] Support more types in new Blob(...) Gives the same treatment to Blob as https://github.com/lightpanda-io/browser/pull/2171 gave to Response. Creating a blob from JS and creating a blob internally is different enough that they now share little, and the ergonomics for creating a blob internally are better / simpler. Conversely, initializing a Response and Blob from a js.Value now share a `js_val.toStringSmart()` which can probably be used in other places. Unlike `toString()` which behaves like JavaScript (e.g. array_buffer.toString() -> "[object ArrayBuffer]"), `toStringSmart` understands buffers and blobs. --- src/browser/js/Value.zig | 47 ++++++ src/browser/tests/blob.html | 94 +++++++++++ src/browser/webapi/Blob.zig | 229 +++++++++++++-------------- src/browser/webapi/net/Request.zig | 7 +- src/browser/webapi/net/Response.zig | 34 +--- src/browser/webapi/net/WebSocket.zig | 2 +- 6 files changed, 256 insertions(+), 157 deletions(-) diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index bf1696e7..b7e8b4ca 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -163,6 +163,53 @@ pub fn isFloat64Array(self: Value) bool { return v8.v8__Value__IsFloat64Array(self.handle); } +// A few places in the code take various types, but want a string. This is a +// type-aware version of toString(). If you do: +// (new ArrayBuffer(100)).toString() +// You'll get "[object ArrayBuffer]". But this `toStringSmart()` knows about +// buffers, and Blobs, etc and will try to return the real underlying string +// value. It _does_ ultimately fallback to toString() - callers should check +// for types they _don't_ want before calling this. For example, `Response` +// checks for null or undefined before calling this to apply specific handling +// to those cases. +pub fn toStringSmart(self: Value) ![]const u8 { + if (self.isString()) |js_str| { + return try js_str.toSlice(); + } + + const Blob = @import("../webapi/Blob.zig"); + if (self.local.jsValueToZig(*Blob, self)) |blob_obj| { + return blob_obj._slice; + } else |_| {} + + var byte_offset: usize = 0; + var byte_len: usize = undefined; + var array_buffer: ?*const v8.ArrayBuffer = null; + + if (self.isTypedArray() or self.isArrayBufferView()) { + const buffer_handle: *const v8.ArrayBufferView = @ptrCast(self.handle); + byte_len = v8.v8__ArrayBufferView__ByteLength(buffer_handle); + byte_offset = v8.v8__ArrayBufferView__ByteOffset(buffer_handle); + array_buffer = v8.v8__ArrayBufferView__Buffer(buffer_handle); + } else if (self.isArrayBuffer()) { + array_buffer = @ptrCast(self.handle); + byte_len = v8.v8__ArrayBuffer__ByteLength(array_buffer); + } else { + return self.toStringSlice(); + } + + const backing_store_ptr = v8.v8__ArrayBuffer__GetBackingStore(array_buffer orelse return ""); + if (byte_len == 0) { + return &[_]u8{}; + } + + const backing_store_handle = v8.std__shared_ptr__v8__BackingStore__get(&backing_store_ptr) orelse return ""; + const data = v8.v8__BackingStore__Data(backing_store_handle) orelse return ""; + const base = @as([*]const u8, @ptrCast(data)) + byte_offset; + + return base[0..byte_len]; +} + pub fn isPromise(self: Value) bool { return v8.v8__Value__IsPromise(self.handle); } diff --git a/src/browser/tests/blob.html b/src/browser/tests/blob.html index 0cbf8ea5..5f59ade7 100644 --- a/src/browser/tests/blob.html +++ b/src/browser/tests/blob.html @@ -79,6 +79,100 @@ } + +