mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Preserve chunk value types through ReadableStream enqueue/read
When JS called controller.enqueue(42), the value was coerced to the string "42" because Chunk only had uint8array and string variants. Add a js_value variant that persists the raw JS value handle, and expose enqueueValue(js.Value) as the JS-facing enqueue method so numbers, booleans, and objects round-trip with their original types.
This commit is contained in:
@@ -301,3 +301,74 @@
|
|||||||
testing.expectEqual(false, data3.done);
|
testing.expectEqual(false, data3.done);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=enqueue_preserves_number>
|
||||||
|
(async function() {
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(42);
|
||||||
|
controller.enqueue(0);
|
||||||
|
controller.enqueue(3.14);
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const reader = stream.getReader();
|
||||||
|
|
||||||
|
const r1 = await reader.read();
|
||||||
|
testing.expectEqual(false, r1.done);
|
||||||
|
testing.expectEqual('number', typeof r1.value);
|
||||||
|
testing.expectEqual(42, r1.value);
|
||||||
|
|
||||||
|
const r2 = await reader.read();
|
||||||
|
testing.expectEqual('number', typeof r2.value);
|
||||||
|
testing.expectEqual(0, r2.value);
|
||||||
|
|
||||||
|
const r3 = await reader.read();
|
||||||
|
testing.expectEqual('number', typeof r3.value);
|
||||||
|
testing.expectEqual(3.14, r3.value);
|
||||||
|
|
||||||
|
const r4 = await reader.read();
|
||||||
|
testing.expectEqual(true, r4.done);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=enqueue_preserves_bool>
|
||||||
|
(async function() {
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(true);
|
||||||
|
controller.enqueue(false);
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const reader = stream.getReader();
|
||||||
|
|
||||||
|
const r1 = await reader.read();
|
||||||
|
testing.expectEqual('boolean', typeof r1.value);
|
||||||
|
testing.expectEqual(true, r1.value);
|
||||||
|
|
||||||
|
const r2 = await reader.read();
|
||||||
|
testing.expectEqual('boolean', typeof r2.value);
|
||||||
|
testing.expectEqual(false, r2.value);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=enqueue_preserves_object>
|
||||||
|
(async function() {
|
||||||
|
const stream = new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue({ key: 'value', num: 7 });
|
||||||
|
controller.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const reader = stream.getReader();
|
||||||
|
|
||||||
|
const r1 = await reader.read();
|
||||||
|
testing.expectEqual('object', typeof r1.value);
|
||||||
|
testing.expectEqual('value', r1.value.key);
|
||||||
|
testing.expectEqual(7, r1.value.num);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -33,11 +33,13 @@ pub const Chunk = union(enum) {
|
|||||||
// the order matters, sorry.
|
// the order matters, sorry.
|
||||||
uint8array: js.TypedArray(u8),
|
uint8array: js.TypedArray(u8),
|
||||||
string: []const u8,
|
string: []const u8,
|
||||||
|
js_value: js.Value.Global,
|
||||||
|
|
||||||
pub fn dupe(self: Chunk, allocator: std.mem.Allocator) !Chunk {
|
pub fn dupe(self: Chunk, allocator: std.mem.Allocator) !Chunk {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.string => |str| .{ .string = try allocator.dupe(u8, str) },
|
.string => |str| .{ .string = try allocator.dupe(u8, str) },
|
||||||
.uint8array => |arr| .{ .uint8array = try arr.dupe(allocator) },
|
.uint8array => |arr| .{ .uint8array = try arr.dupe(allocator) },
|
||||||
|
.js_value => |val| .{ .js_value = val },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -98,6 +100,40 @@ pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
|
|||||||
ls.toLocal(resolver).resolve("stream enqueue", result);
|
ls.toLocal(resolver).resolve("stream enqueue", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enqueue a raw JS value, preserving its type (number, bool, object, etc.).
|
||||||
|
/// Used by the JS-facing API; internal Zig callers should use enqueue(Chunk).
|
||||||
|
pub fn enqueueValue(self: *ReadableStreamDefaultController, value: js.Value) !void {
|
||||||
|
if (self._stream._state != .readable) {
|
||||||
|
return error.StreamNotReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self._pending_reads.items.len == 0) {
|
||||||
|
const persisted = try value.persist();
|
||||||
|
try self._queue.append(self._arena, .{ .js_value = persisted });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolver = self._pending_reads.orderedRemove(0);
|
||||||
|
const persisted = try value.persist();
|
||||||
|
const result = ReadableStreamDefaultReader.ReadResult{
|
||||||
|
.done = false,
|
||||||
|
.value = .{ .js_value = persisted },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
if (self._page.js.local == null) {
|
||||||
|
log.fatal(.bug, "null context scope", .{ .src = "ReadableStreamDefaultController.enqueueValue", .url = self._page.url });
|
||||||
|
std.debug.assert(self._page.js.local != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self._page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
ls.toLocal(resolver).resolve("stream enqueue value", result);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn close(self: *ReadableStreamDefaultController) !void {
|
pub fn close(self: *ReadableStreamDefaultController) !void {
|
||||||
if (self._stream._state != .readable) {
|
if (self._stream._state != .readable) {
|
||||||
return error.StreamNotReadable;
|
return error.StreamNotReadable;
|
||||||
@@ -176,7 +212,7 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const enqueue = bridge.function(ReadableStreamDefaultController.enqueue, .{});
|
pub const enqueue = bridge.function(ReadableStreamDefaultController.enqueueValue, .{});
|
||||||
pub const close = bridge.function(ReadableStreamDefaultController.close, .{});
|
pub const close = bridge.function(ReadableStreamDefaultController.close, .{});
|
||||||
pub const @"error" = bridge.function(ReadableStreamDefaultController.doError, .{});
|
pub const @"error" = bridge.function(ReadableStreamDefaultController.doError, .{});
|
||||||
pub const desiredSize = bridge.accessor(ReadableStreamDefaultController.getDesiredSize, null, .{});
|
pub const desiredSize = bridge.accessor(ReadableStreamDefaultController.getDesiredSize, null, .{});
|
||||||
|
|||||||
@@ -44,11 +44,13 @@ pub const ReadResult = struct {
|
|||||||
empty,
|
empty,
|
||||||
string: []const u8,
|
string: []const u8,
|
||||||
uint8array: js.TypedArray(u8),
|
uint8array: js.TypedArray(u8),
|
||||||
|
js_value: js.Value.Global,
|
||||||
|
|
||||||
pub fn fromChunk(chunk: ReadableStreamDefaultController.Chunk) Chunk {
|
pub fn fromChunk(chunk: ReadableStreamDefaultController.Chunk) Chunk {
|
||||||
return switch (chunk) {
|
return switch (chunk) {
|
||||||
.string => |s| .{ .string = s },
|
.string => |s| .{ .string = s },
|
||||||
.uint8array => |arr| .{ .uint8array = arr },
|
.uint8array => |arr| .{ .uint8array = arr },
|
||||||
|
.js_value => |val| .{ .js_value = val },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -178,6 +178,11 @@ pub const TransformStreamDefaultController = struct {
|
|||||||
try self._stream._readable._controller.enqueue(chunk);
|
try self._stream._readable._controller.enqueue(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enqueue a raw JS value, preserving its type. Used by the JS-facing API.
|
||||||
|
pub fn enqueueValue(self: *TransformStreamDefaultController, value: js.Value) !void {
|
||||||
|
try self._stream._readable._controller.enqueueValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn doError(self: *TransformStreamDefaultController, reason: []const u8) !void {
|
pub fn doError(self: *TransformStreamDefaultController, reason: []const u8) !void {
|
||||||
try self._stream._readable._controller.doError(reason);
|
try self._stream._readable._controller.doError(reason);
|
||||||
}
|
}
|
||||||
@@ -195,7 +200,7 @@ pub const TransformStreamDefaultController = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const enqueue = bridge.function(TransformStreamDefaultController.enqueue, .{});
|
pub const enqueue = bridge.function(TransformStreamDefaultController.enqueueValue, .{});
|
||||||
pub const @"error" = bridge.function(TransformStreamDefaultController.doError, .{});
|
pub const @"error" = bridge.function(TransformStreamDefaultController.doError, .{});
|
||||||
pub const terminate = bridge.function(TransformStreamDefaultController.terminate, .{});
|
pub const terminate = bridge.function(TransformStreamDefaultController.terminate, .{});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user