mirror of
https://github.com/oven-sh/bun
synced 2026-02-15 21:32:05 +00:00
Reduce the size of `bun.webcore.Blob` from 120 bytes to 96. Also make it ref-counted: in-progress work on improving the bindings generator depends on this, as it means C++ can pass a pointer to the `Blob` to Zig without risking it being destroyed if the GC collects the associated `JSBlob`. Note that this PR depends on #23013. (For internal tracking: fixes STAB-1289, STAB-1290)
177 lines
5.8 KiB
Zig
177 lines
5.8 KiB
Zig
const ObjectURLRegistry = @This();
|
|
|
|
lock: bun.Mutex = .{},
|
|
map: std.AutoHashMap(UUID, *Entry) = std.AutoHashMap(UUID, *Entry).init(bun.default_allocator),
|
|
|
|
pub const Entry = struct {
|
|
blob: jsc.WebCore.Blob,
|
|
|
|
pub const new = bun.TrivialNew(@This());
|
|
pub fn init(blob: *const jsc.WebCore.Blob) *Entry {
|
|
return Entry.new(.{
|
|
.blob = blob.dupeWithContentType(true),
|
|
});
|
|
}
|
|
|
|
pub fn deinit(this: *Entry) void {
|
|
this.blob.deinit();
|
|
bun.destroy(this);
|
|
}
|
|
};
|
|
|
|
pub fn register(this: *ObjectURLRegistry, vm: *jsc.VirtualMachine, blob: *const jsc.WebCore.Blob) UUID {
|
|
const uuid = vm.rareData().nextUUID();
|
|
const entry = Entry.init(blob);
|
|
|
|
this.lock.lock();
|
|
defer this.lock.unlock();
|
|
bun.handleOom(this.map.put(uuid, entry));
|
|
return uuid;
|
|
}
|
|
|
|
pub fn singleton() *ObjectURLRegistry {
|
|
const Singleton = struct {
|
|
pub var registry: ObjectURLRegistry = undefined;
|
|
pub var once = std.once(get);
|
|
|
|
fn get() void {
|
|
registry = .{};
|
|
}
|
|
};
|
|
|
|
Singleton.once.call();
|
|
|
|
return &Singleton.registry;
|
|
}
|
|
|
|
fn getDupedBlob(this: *ObjectURLRegistry, uuid: *const UUID) ?jsc.WebCore.Blob {
|
|
this.lock.lock();
|
|
defer this.lock.unlock();
|
|
const entry = this.map.get(uuid.*) orelse return null;
|
|
return entry.blob.dupeWithContentType(true);
|
|
}
|
|
|
|
fn uuidFromPathname(pathname: []const u8) ?UUID {
|
|
return UUID.parse(pathname) catch return null;
|
|
}
|
|
|
|
pub fn resolveAndDupe(this: *ObjectURLRegistry, pathname: []const u8) ?jsc.WebCore.Blob {
|
|
const uuid = uuidFromPathname(pathname) orelse return null;
|
|
this.lock.lock();
|
|
defer this.lock.unlock();
|
|
const entry = this.map.get(uuid) orelse return null;
|
|
return entry.blob.dupeWithContentType(true);
|
|
}
|
|
|
|
pub fn resolveAndDupeToJS(this: *ObjectURLRegistry, pathname: []const u8, globalObject: *jsc.JSGlobalObject) ?jsc.JSValue {
|
|
var blob = jsc.WebCore.Blob.new(this.resolveAndDupe(pathname) orelse return null);
|
|
return blob.toJS(globalObject);
|
|
}
|
|
|
|
pub fn revoke(this: *ObjectURLRegistry, pathname: []const u8) void {
|
|
const uuid = uuidFromPathname(pathname) orelse return;
|
|
this.lock.lock();
|
|
defer this.lock.unlock();
|
|
const entry = this.map.fetchRemove(uuid) orelse return;
|
|
entry.value.deinit();
|
|
}
|
|
|
|
pub fn has(this: *ObjectURLRegistry, pathname: []const u8) bool {
|
|
const uuid = uuidFromPathname(pathname) orelse return false;
|
|
this.lock.lock();
|
|
defer this.lock.unlock();
|
|
return this.map.contains(uuid);
|
|
}
|
|
|
|
comptime {
|
|
const Bun__createObjectURL = jsc.toJSHostFn(Bun__createObjectURL_);
|
|
@export(&Bun__createObjectURL, .{ .name = "Bun__createObjectURL" });
|
|
}
|
|
fn Bun__createObjectURL_(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
|
const arguments = callframe.arguments_old(1);
|
|
if (arguments.len < 1) {
|
|
return globalObject.throwNotEnoughArguments("createObjectURL", 1, arguments.len);
|
|
}
|
|
const blob = arguments.ptr[0].as(jsc.WebCore.Blob) orelse {
|
|
return globalObject.throwInvalidArguments("createObjectURL expects a Blob object", .{});
|
|
};
|
|
const registry = ObjectURLRegistry.singleton();
|
|
const uuid = registry.register(globalObject.bunVM(), blob);
|
|
var str = bun.handleOom(bun.String.createFormat("blob:{}", .{uuid}));
|
|
return str.transferToJS(globalObject);
|
|
}
|
|
|
|
comptime {
|
|
const Bun__revokeObjectURL = jsc.toJSHostFn(Bun__revokeObjectURL_);
|
|
@export(&Bun__revokeObjectURL, .{ .name = "Bun__revokeObjectURL" });
|
|
}
|
|
fn Bun__revokeObjectURL_(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
|
const arguments = callframe.arguments_old(1);
|
|
if (arguments.len < 1) {
|
|
return globalObject.throwNotEnoughArguments("revokeObjectURL", 1, arguments.len);
|
|
}
|
|
if (!arguments.ptr[0].isString()) {
|
|
return globalObject.throwInvalidArguments("revokeObjectURL expects a string", .{});
|
|
}
|
|
const str = arguments.ptr[0].toBunString(globalObject) catch @panic("unreachable");
|
|
if (!str.hasPrefixComptime("blob:")) {
|
|
return .js_undefined;
|
|
}
|
|
|
|
const slice = str.toUTF8WithoutRef(bun.default_allocator);
|
|
defer slice.deinit();
|
|
defer str.deref();
|
|
|
|
const sliced = slice.slice();
|
|
if (sliced.len < "blob:".len + UUID.stringLength) {
|
|
return .js_undefined;
|
|
}
|
|
ObjectURLRegistry.singleton().revoke(sliced["blob:".len..]);
|
|
return .js_undefined;
|
|
}
|
|
|
|
comptime {
|
|
const jsFunctionResolveObjectURL = jsc.toJSHostFn(jsFunctionResolveObjectURL_);
|
|
@export(&jsFunctionResolveObjectURL, .{ .name = "jsFunctionResolveObjectURL" });
|
|
}
|
|
fn jsFunctionResolveObjectURL_(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
|
const arguments = callframe.arguments_old(1);
|
|
|
|
// Errors are ignored.
|
|
// Not thrown.
|
|
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/internal/blob.js#L441
|
|
if (arguments.len < 1) {
|
|
return .js_undefined;
|
|
}
|
|
const str = try arguments.ptr[0].toBunString(globalObject);
|
|
defer str.deref();
|
|
|
|
if (globalObject.hasException()) {
|
|
return .zero;
|
|
}
|
|
|
|
if (!str.hasPrefixComptime("blob:") or str.length() < specifier_len) {
|
|
return .js_undefined;
|
|
}
|
|
|
|
const slice = str.toUTF8WithoutRef(bun.default_allocator);
|
|
defer slice.deinit();
|
|
const sliced = slice.slice();
|
|
|
|
const registry = ObjectURLRegistry.singleton();
|
|
const blob = registry.resolveAndDupeToJS(sliced["blob:".len..], globalObject);
|
|
return blob orelse .js_undefined;
|
|
}
|
|
|
|
pub const specifier_len = "blob:".len + UUID.stringLength;
|
|
|
|
pub fn isBlobURL(url: []const u8) bool {
|
|
return url.len >= specifier_len and bun.strings.hasPrefixComptime(url, "blob:");
|
|
}
|
|
|
|
const std = @import("std");
|
|
|
|
const bun = @import("bun");
|
|
const UUID = bun.UUID;
|
|
const jsc = bun.jsc;
|