webapi: address DataTransfer review feedback

- DataTransferItem/DataTransferItemList/Iterator forward acquireRef/releaseRef
  to the owning DataTransfer, so its pooled arena outlives any JS-held wrapper
  (fixes a potential use-after-free when JS keeps an item/list/iterator alive
  past the DataTransfer).
- addItem reads strings straight into the persistent arena via
  toStringSliceWithAlloc, removing the intermediate call_arena copy.
- normalizeFormat uses std.ascii.allocLowerString instead of dupe + lowerString.
- setData: rename dup -> owned_data for clarity.
This commit is contained in:
Navid EMAD
2026-06-10 12:33:22 +02:00
parent ff1fb2dc0d
commit fefc1931b9
3 changed files with 38 additions and 9 deletions

View File

@@ -98,8 +98,7 @@ fn normalizeFormat(arena: Allocator, format: []const u8) ![]const u8 {
if (std.ascii.eqlIgnoreCase(format, "url")) {
return "text/uri-list";
}
const buf = try arena.dupe(u8, format);
return std.ascii.lowerString(buf, buf);
return std.ascii.allocLowerString(arena, format);
}
pub fn getData(self: *const DataTransfer, format: []const u8, frame: *Frame) ![]const u8 {
@@ -114,15 +113,15 @@ pub fn getData(self: *const DataTransfer, format: []const u8, frame: *Frame) ![]
pub fn setData(self: *DataTransfer, format: []const u8, data: []const u8) !void {
const norm = try normalizeFormat(self._arena, format);
const dup = try self._arena.dupe(u8, data);
const owned_data = try self._arena.dupe(u8, data);
for (self._items.items) |it| {
if (it._kind == .string and std.mem.eql(u8, it._type, norm)) {
it._payload = .{ .string = dup };
it._payload = .{ .string = owned_data };
return;
}
}
const it = try self._arena.create(DataTransferItem);
it.* = .{ ._kind = .string, ._type = norm, ._payload = .{ .string = dup } };
it.* = .{ ._data_transfer = self, ._kind = .string, ._type = norm, ._payload = .{ .string = owned_data } };
try self._items.append(self._arena, it);
}
@@ -159,11 +158,10 @@ pub fn addItem(self: *DataTransfer, data: js.Value, type_: ?[]const u8, frame: *
return try self.addFileItem(file, frame);
} else |_| {}
const s = try data.toZig([]const u8);
const owned_data = try data.toStringSliceWithAlloc(self._arena);
const norm = try normalizeFormat(self._arena, type_ orelse "");
const dup = try self._arena.dupe(u8, s);
const it = try self._arena.create(DataTransferItem);
it.* = .{ ._kind = .string, ._type = norm, ._payload = .{ .string = dup } };
it.* = .{ ._data_transfer = self, ._kind = .string, ._type = norm, ._payload = .{ .string = owned_data } };
try self._items.append(self._arena, it);
return it;
}
@@ -171,7 +169,7 @@ pub fn addItem(self: *DataTransfer, data: js.Value, type_: ?[]const u8, frame: *
fn addFileItem(self: *DataTransfer, file: *File, frame: *Frame) !*DataTransferItem {
file._proto.acquireRef();
const it = try self._arena.create(DataTransferItem);
it.* = .{ ._kind = .file, ._type = file._proto.getType(), ._payload = .{ .file = file } };
it.* = .{ ._data_transfer = self, ._kind = .file, ._type = file._proto.getType(), ._payload = .{ .file = file } };
try self._items.append(self._arena, it);
try self.rebuildFiles(frame);
return it;

View File

@@ -22,6 +22,8 @@ const lp = @import("lightpanda");
const js = @import("../js/js.zig");
const File = @import("File.zig");
const Page = @import("../Page.zig");
const DataTransfer = @import("DataTransfer.zig");
const log = lp.log;
@@ -30,6 +32,10 @@ const DataTransferItem = @This();
pub const Kind = enum { string, file };
// The owning DataTransfer. The item lives in that DataTransfer's arena and is
// handed to JS, so it forwards acquireRef/releaseRef to keep the arena alive as
// long as JS holds the item.
_data_transfer: *DataTransfer,
_kind: Kind,
// For string items: the normalized format (e.g. "text/plain").
// For file items: the File's MIME type.
@@ -41,6 +47,14 @@ pub const Payload = union(Kind) {
file: *File,
};
pub fn acquireRef(self: *DataTransferItem) void {
self._data_transfer.acquireRef();
}
pub fn releaseRef(self: *DataTransferItem, page: *Page) void {
self._data_transfer.releaseRef(page);
}
pub fn getKind(self: *const DataTransferItem) []const u8 {
return switch (self._kind) {
.string => "string",

View File

@@ -18,6 +18,7 @@
const js = @import("../js/js.zig");
const Frame = @import("../Frame.zig");
const Page = @import("../Page.zig");
const DataTransfer = @import("DataTransfer.zig");
const DataTransferItem = @import("DataTransferItem.zig");
@@ -30,6 +31,14 @@ const DataTransferItemList = @This();
_data_transfer: *DataTransfer,
pub fn acquireRef(self: *DataTransferItemList) void {
self._data_transfer.acquireRef();
}
pub fn releaseRef(self: *DataTransferItemList, page: *Page) void {
self._data_transfer.releaseRef(page);
}
pub fn getLength(self: *const DataTransferItemList) u32 {
return @intCast(self._data_transfer._items.items.len);
}
@@ -69,6 +78,14 @@ pub const Iterator = GenericIterator(struct {
index: u32,
list: *DataTransferItemList,
pub fn acquireRef(self: *@This()) void {
self.list.acquireRef();
}
pub fn releaseRef(self: *@This(), page: *Page) void {
self.list.releaseRef(page);
}
pub fn next(self: *@This(), _: *const js.Execution) ?*DataTransferItem {
const index = self.index;
const it = self.list.item(index) orelse return null;