port remaining blob functionality

This commit is contained in:
Karl Seguin
2025-12-11 11:46:59 +08:00
parent f0d9d53588
commit 695ed817e4
3 changed files with 105 additions and 68 deletions

View File

@@ -196,8 +196,8 @@ pub fn blob(self: *Factory, child: anytype) !*@TypeOf(child) {
const blob_ptr = chain.get(0);
blob_ptr.* = .{
._type = unionInit(Blob.Type, chain.get(1)),
.slice = "",
.mime = "",
._slice = "",
._mime = "",
};
chain.setLeaf(1, child);

View File

@@ -1,8 +1,6 @@
<!DOCTYPE html>
<head id="the_head">
<title>Test Document Title</title>
<script src="./testing.js"></script>
</head>
<meta charset="UTF-8">
<script src="./testing.js"></script>
<script id=basic>
{
@@ -13,7 +11,7 @@
const expected = parts.join("");
testing.expectEqual(expected.length, blob.size);
testing.expectEqual("text/html", blob.type);
testing.async(blob.text(), result => testing.expectEqual(expected, result));
testing.async(async () => { testing.expectEqual(expected, await blob.text()) });
}
{
@@ -23,34 +21,7 @@
const expected = "\nhello\n\nwor\nld";
testing.expectEqual(expected.length, blob.size);
testing.async(blob.text(), result => testing.expectEqual(expected, result));
}
</script>
<script id=slice>
{
const parts = ["la", "symphonie", "des", "éclairs"];
const blob = new Blob(parts);
let temp = blob.slice(0);
testing.expectEqual(blob.size, temp.size);
testing.async(temp.text(), result => {
testing.expectEqual("lasymphoniedeséclairs", result);
});
temp = blob.slice(-4, -2, "custom");
testing.expectEqual(2, temp.size);
testing.expectEqual("custom", temp.type);
testing.async(temp.text(), result => testing.expectEqual("ai", result));
temp = blob.slice(14);
testing.expectEqual(8, temp.size);
testing.async(temp.text(), result => testing.expectEqual("éclairs", result));
temp = blob.slice(6, -10, "text/eclair");
testing.expectEqual(6, temp.size);
testing.expectEqual("text/eclair", temp.type);
testing.async(temp.text(), result => testing.expectEqual("honied", result));
testing.async(async () => { testing.expectEqual(expected, await blob.text()) });
}
</script>
@@ -60,10 +31,11 @@
const parts = ["light ", "panda ", "rocks ", "!"];
const blob = new Blob(parts);
testing.async(blob.bytes(), result => {
testing.async(async() => {
const expected = new Uint8Array([108, 105, 103, 104, 116, 32, 112, 97,
110, 100, 97, 32, 114, 111, 99, 107, 115,
32, 33]);
const result = await blob.bytes();
testing.expectEqual(true, result instanceof Uint8Array);
testing.expectEqual(expected, result);
});
@@ -81,7 +53,7 @@
const blob = new Blob(parts, { type: "text/html", endings: "native" });
testing.expectEqual(161, blob.size);
testing.expectEqual("text/html", blob.type);
testing.async(blob.bytes(), result => {
testing.async(async() => {
const expected = new Uint8Array([10, 84, 104, 101, 32, 111, 112, 101, 110,
101, 100, 32, 112, 97, 99, 107, 97, 103,
101, 10, 111, 102, 32, 112, 111, 116, 97,
@@ -100,8 +72,65 @@
10, 10, 116, 111, 32, 115, 111, 108, 118, 101,
32, 116, 104, 101, 32, 10, 99, 114, 105, 109,
101, 46, 10]);
const result = await blob.bytes();
testing.expectEqual(true, result instanceof Uint8Array);
testing.expectEqual(expected, result);
});
}
</script>
<script id=stream>
{
const parts = ["may", "thy", "knife", "chip", "and", "shatter"];
const blob = new Blob(parts);
const reader = blob.stream().getReader();
testing.async(async () => {
const {done: done, value: value} = await reader.read()
const expected = new Uint8Array([109, 97, 121, 116, 104, 121, 107, 110,
105, 102, 101, 99, 104, 105, 112, 97,
110, 100, 115, 104, 97, 116, 116, 101,
114]);
testing.expectEqual(false, done);
testing.expectEqual(true, value instanceof Uint8Array);
testing.expectEqual(expected, value);
});
}
</script>
<script id=slice>
{
const parts = ["la", "symphonie", "des", "éclairs"];
const blob = new Blob(parts);
testing.async(async () => {
const result = await blob.arrayBuffer();
testing.expectEqual(true, result instanceof ArrayBuffer)
});
let temp = blob.slice(0);
testing.expectEqual(blob.size, temp.size);
testing.async(async () => {
testing.expectEqual("lasymphoniedeséclairs", await temp.text());
});
temp = blob.slice(-4, -2, "custom");
testing.expectEqual(2, temp.size);
testing.expectEqual("custom", temp.type);
testing.async(async () => {
testing.expectEqual("ai", await temp.text());
});
temp = blob.slice(14);
testing.expectEqual(8, temp.size);
testing.async(async () => {
testing.expectEqual("éclairs", await temp.text());
});
temp = blob.slice(6, -10, "text/eclair");
testing.expectEqual(6, temp.size);
testing.expectEqual("text/eclair", temp.type);
testing.async(async () => {
testing.expectEqual("honied", await temp.text());
});
}
</script>

View File

@@ -27,14 +27,15 @@ const Page = @import("../Page.zig");
const Blob = @This();
const _prototype_root = true;
_type: Type,
/// 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.
slice: []const u8,
_slice: []const u8,
/// MIME attached to blob. Can be an empty string.
mime: []const u8,
_mime: []const u8,
pub const Type = union(enum) {
generic,
@@ -66,7 +67,7 @@ pub fn init(
break :blk try page.arena.dupe(u8, t);
};
const slice = blk: {
const data = blk: {
if (maybe_blob_parts) |blob_parts| {
var w: Writer.Allocating = .init(page.arena);
const use_native_endings = std.mem.eql(u8, options.endings, "native");
@@ -80,8 +81,8 @@ pub fn init(
return page._factory.create(Blob{
._type = .generic,
.slice = slice,
.mime = mime,
._slice = data,
._mime = mime,
});
}
@@ -147,8 +148,8 @@ fn writeBlobParts(
while (end + vector_len <= part.len) : (end += vector_len) {
const cr: Vec = @splat('\r');
// Load chunk as vectors.
const slice = part[end..][0..vector_len];
const chunk: Vec = slice.*;
const data = part[end..][0..vector_len];
const chunk: Vec = data.*;
// Look for CR.
const match = chunk == cr;
@@ -160,16 +161,16 @@ fn writeBlobParts(
var iter = bitset.iterator(.{});
var relative_start: usize = 0;
while (iter.next()) |index| {
_ = try writer.writeVec(&.{ slice[relative_start..index], "\n" });
_ = try writer.writeVec(&.{ data[relative_start..index], "\n" });
if (index + 1 != slice.len and slice[index + 1] == '\n') {
if (index + 1 != data.len and data[index + 1] == '\n') {
relative_start = index + 2;
} else {
relative_start = index + 1;
}
}
_ = try writer.writeVec(&.{slice[relative_start..]});
_ = try writer.writeVec(&.{data[relative_start..]});
}
}
@@ -204,16 +205,21 @@ fn writeBlobParts(
/// Returns a Promise that resolves with the contents of the blob
/// as binary data contained in an ArrayBuffer.
//pub fn arrayBuffer(self: *const Blob, page: *Page) !js.Promise {
// return page.js.resolvePromise(js.ArrayBuffer{ .values = self.slice });
//}
pub fn arrayBuffer(self: *const Blob, page: *Page) !js.Promise {
return page.js.resolvePromise(js.ArrayBuffer{ .values = self._slice });
}
// TODO: Implement `stream`; requires `ReadableStream`.
const ReadableStream = @import("streams/ReadableStream.zig");
/// Returns a ReadableStream which upon reading returns the data
/// contained within the Blob.
pub fn stream(self: *const Blob, page: *Page) !*ReadableStream {
return ReadableStream.initWithData(self._slice, page);
}
/// Returns a Promise that resolves with a string containing
/// the contents of the blob, interpreted as UTF-8.
pub fn text(self: *const Blob, page: *Page) !js.Promise {
return page.js.resolvePromise(self.slice);
return page.js.resolvePromise(self._slice);
}
/// Extension to Blob; works on Firefox and Safari.
@@ -221,12 +227,12 @@ pub fn text(self: *const Blob, page: *Page) !js.Promise {
/// Returns a Promise that resolves with a Uint8Array containing
/// the contents of the blob as an array of bytes.
pub fn bytes(self: *const Blob, page: *Page) !js.Promise {
return page.js.resolvePromise(js.TypedArray(u8){ .values = self.slice });
return page.js.resolvePromise(js.TypedArray(u8){ .values = self._slice });
}
/// Returns a new Blob object which contains data
/// from a subset of the blob on which it's called.
pub fn getSlice(
pub fn slice(
self: *const Blob,
maybe_start: ?i32,
maybe_end: ?i32,
@@ -239,56 +245,56 @@ pub fn getSlice(
break :blk "";
}
break :blk try page.arena.dupe(u8, content_type);
break :blk try page.dupeString(content_type);
}
break :blk "";
};
const slice = self.slice;
const data = self._slice;
if (maybe_start) |_start| {
const start = blk: {
if (_start < 0) {
break :blk slice.len -| @abs(_start);
break :blk data.len -| @abs(_start);
}
break :blk @min(slice.len, @as(u31, @intCast(_start)));
break :blk @min(data.len, @as(u31, @intCast(_start)));
};
const end: usize = blk: {
if (maybe_end) |_end| {
if (_end < 0) {
break :blk @max(start, slice.len -| @abs(_end));
break :blk @max(start, data.len -| @abs(_end));
}
break :blk @min(slice.len, @max(start, @as(u31, @intCast(_end))));
break :blk @min(data.len, @max(start, @as(u31, @intCast(_end))));
}
break :blk slice.len;
break :blk data.len;
};
return page._factory.create(Blob{
._type = .generic,
.slice = slice[start..end],
.mime = mime,
._slice = data[start..end],
._mime = mime,
});
}
return page._factory.create(Blob{
._type = .generic,
.slice = slice,
.mime = mime,
._slice = data,
._mime = mime,
});
}
/// Returns the size of the Blob in bytes.
pub fn getSize(self: *const Blob) usize {
return self.slice.len;
return self._slice.len;
}
/// Returns the type of Blob; likely a MIME type, yet anything can be given.
pub fn getType(self: *const Blob) []const u8 {
return self.mime;
return self._mime;
}
pub const JsApi = struct {
@@ -303,9 +309,11 @@ pub const JsApi = struct {
pub const constructor = bridge.constructor(Blob.init, .{});
pub const text = bridge.function(Blob.text, .{});
pub const bytes = bridge.function(Blob.bytes, .{});
pub const slice = bridge.function(Blob.getSlice, .{});
pub const slice = bridge.function(Blob.slice, .{});
pub const size = bridge.accessor(Blob.getSize, null, .{});
pub const @"type" = bridge.accessor(Blob.getType, null, .{});
pub const stream = bridge.function(Blob.stream, .{});
pub const arrayBuffer = bridge.function(Blob.arrayBuffer, .{});
};
const testing = @import("../../testing.zig");