Merge pull request #1775 from lightpanda-io/arena_blob

Use arena from ArenaPool for Blob (and File)
This commit is contained in:
Pierre Tachoire
2026-03-11 08:35:27 +01:00
committed by GitHub
3 changed files with 56 additions and 54 deletions

View File

@@ -247,16 +247,15 @@ fn eventInit(arena: Allocator, typ: String, value: anytype) !Event {
};
}
pub fn blob(self: *Factory, child: anytype) !*@TypeOf(child) {
const allocator = self._slab.allocator();
pub fn blob(_: *const Factory, arena: Allocator, child: anytype) !*@TypeOf(child) {
// Special case: Blob has slice and mime fields, so we need manual setup
const chain = try PrototypeChain(
&.{ Blob, @TypeOf(child) },
).allocate(allocator);
).allocate(arena);
const blob_ptr = chain.get(0);
blob_ptr.* = .{
._arena = arena,
._type = unionInit(Blob.Type, chain.get(1)),
._slice = "",
._mime = "",

View File

@@ -21,8 +21,12 @@ const Writer = std.Io.Writer;
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const Mime = @import("../Mime.zig");
const Allocator = std.mem.Allocator;
/// https://w3c.github.io/FileAPI/#blob-section
/// https://developer.mozilla.org/en-US/docs/Web/API/Blob
const Blob = @This();
@@ -31,6 +35,8 @@ pub const _prototype_root = true;
_type: Type,
_arena: Allocator,
/// Immutable slice of blob.
/// Note that another blob may hold a pointer/slice to this,
/// so its better to leave the deallocation of it to arena allocator.
@@ -69,6 +75,9 @@ pub fn initWithMimeValidation(
validate_mime: bool,
page: *Page,
) !*Blob {
const arena = try page.getArena(.{ .debug = "Blob" });
errdefer page.releaseArena(arena);
const options: InitOptions = maybe_options orelse .{};
const mime: []const u8 = blk: {
@@ -77,7 +86,7 @@ pub fn initWithMimeValidation(
break :blk "";
}
const buf = try page.arena.dupe(u8, t);
const buf = try arena.dupe(u8, t);
if (validate_mime) {
// Full MIME parsing per MIME sniff spec (for Content-Type headers)
@@ -99,7 +108,7 @@ pub fn initWithMimeValidation(
const data = blk: {
if (maybe_blob_parts) |blob_parts| {
var w: Writer.Allocating = .init(page.arena);
var w: Writer.Allocating = .init(arena);
const use_native_endings = std.mem.eql(u8, options.endings, "native");
try writeBlobParts(&w.writer, blob_parts, use_native_endings);
@@ -109,11 +118,19 @@ pub fn initWithMimeValidation(
break :blk "";
};
return page._factory.create(Blob{
const self = try arena.create(Blob);
self.* = .{
._arena = arena,
._type = .generic,
._slice = data,
._mime = mime,
});
};
return self;
}
pub fn deinit(self: *Blob, shutdown: bool, session: *Session) void {
_ = shutdown;
session.releaseArena(self._arena);
}
const largest_vector = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
@@ -264,57 +281,31 @@ pub fn bytes(self: *const Blob, page: *Page) !js.Promise {
/// from a subset of the blob on which it's called.
pub fn slice(
self: *const Blob,
maybe_start: ?i32,
maybe_end: ?i32,
maybe_content_type: ?[]const u8,
start_: ?i32,
end_: ?i32,
content_type_: ?[]const u8,
page: *Page,
) !*Blob {
const mime: []const u8 = blk: {
if (maybe_content_type) |content_type| {
if (content_type.len == 0) {
break :blk "";
}
const data = self._slice;
break :blk try page.dupeString(content_type);
const start = blk: {
const requested_start = start_ orelse break :blk 0;
if (requested_start < 0) {
break :blk data.len -| @abs(requested_start);
}
break :blk "";
break :blk @min(data.len, @as(u31, @intCast(requested_start)));
};
const data = self._slice;
if (maybe_start) |_start| {
const start = blk: {
if (_start < 0) {
break :blk data.len -| @abs(_start);
}
const end: usize = blk: {
const requested_end = end_ orelse break :blk data.len;
if (requested_end < 0) {
break :blk @max(start, data.len -| @abs(requested_end));
}
break :blk @min(data.len, @as(u31, @intCast(_start)));
};
break :blk @min(data.len, @max(start, @as(u31, @intCast(requested_end))));
};
const end: usize = blk: {
if (maybe_end) |_end| {
if (_end < 0) {
break :blk @max(start, data.len -| @abs(_end));
}
break :blk @min(data.len, @max(start, @as(u31, @intCast(_end))));
}
break :blk data.len;
};
return page._factory.create(Blob{
._type = .generic,
._slice = data[start..end],
._mime = mime,
});
}
return page._factory.create(Blob{
._type = .generic,
._slice = data,
._mime = mime,
});
return Blob.init(&.{data[start..end]}, .{ .type = content_type_ orelse "" }, page);
}
/// Returns the size of the Blob in bytes.
@@ -334,6 +325,8 @@ pub const JsApi = struct {
pub const name = "Blob";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Blob.deinit);
};
pub const constructor = bridge.constructor(Blob.init, .{});

View File

@@ -18,9 +18,11 @@
const std = @import("std");
const Page = @import("../Page.zig");
const Blob = @import("Blob.zig");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const Blob = @import("Blob.zig");
const File = @This();
@@ -29,7 +31,13 @@ _proto: *Blob,
// TODO: Implement File API.
pub fn init(page: *Page) !*File {
return page._factory.blob(File{ ._proto = undefined });
const arena = try page.getArena(.{ .debug = "File" });
errdefer page.releaseArena(arena);
return page._factory.blob(arena, File{ ._proto = undefined });
}
pub fn deinit(self: *File, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub const JsApi = struct {
@@ -39,6 +47,8 @@ pub const JsApi = struct {
pub const name = "File";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(File.deinit);
};
pub const constructor = bridge.constructor(File.init, .{});