mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Add TextDecoderStream to decode UTF-8 byte streams into strings
Mirrors TextEncoderStream: wraps a TransformStream with a Zig-level transform that converts Uint8Array chunks to strings. Supports the same constructor options as TextDecoder (label, fatal, ignoreBOM).
This commit is contained in:
@@ -827,6 +827,7 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/encoding/TextDecoder.zig"),
|
@import("../webapi/encoding/TextDecoder.zig"),
|
||||||
@import("../webapi/encoding/TextEncoder.zig"),
|
@import("../webapi/encoding/TextEncoder.zig"),
|
||||||
@import("../webapi/encoding/TextEncoderStream.zig"),
|
@import("../webapi/encoding/TextEncoderStream.zig"),
|
||||||
|
@import("../webapi/encoding/TextDecoderStream.zig"),
|
||||||
@import("../webapi/Event.zig"),
|
@import("../webapi/Event.zig"),
|
||||||
@import("../webapi/event/CompositionEvent.zig"),
|
@import("../webapi/event/CompositionEvent.zig"),
|
||||||
@import("../webapi/event/CustomEvent.zig"),
|
@import("../webapi/event/CustomEvent.zig"),
|
||||||
|
|||||||
61
src/browser/tests/streams/text_decoder_stream.html
Normal file
61
src/browser/tests/streams/text_decoder_stream.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
|
<script id=text_decoder_stream_encoding>
|
||||||
|
{
|
||||||
|
const tds = new TextDecoderStream();
|
||||||
|
testing.expectEqual('utf-8', tds.encoding);
|
||||||
|
testing.expectEqual('object', typeof tds.readable);
|
||||||
|
testing.expectEqual('object', typeof tds.writable);
|
||||||
|
testing.expectEqual(false, tds.fatal);
|
||||||
|
testing.expectEqual(false, tds.ignoreBOM);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=text_decoder_stream_with_label>
|
||||||
|
{
|
||||||
|
const tds = new TextDecoderStream('utf-8');
|
||||||
|
testing.expectEqual('utf-8', tds.encoding);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=text_decoder_stream_with_opts>
|
||||||
|
{
|
||||||
|
const tds = new TextDecoderStream('utf-8', { fatal: true, ignoreBOM: true });
|
||||||
|
testing.expectEqual(true, tds.fatal);
|
||||||
|
testing.expectEqual(true, tds.ignoreBOM);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=text_decoder_stream_invalid_label>
|
||||||
|
{
|
||||||
|
let errorThrown = false;
|
||||||
|
try {
|
||||||
|
new TextDecoderStream('windows-1252');
|
||||||
|
} catch (e) {
|
||||||
|
errorThrown = true;
|
||||||
|
}
|
||||||
|
testing.expectEqual(true, errorThrown);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=text_decoder_stream_decode>
|
||||||
|
(async function() {
|
||||||
|
const tds = new TextDecoderStream();
|
||||||
|
|
||||||
|
const writer = tds.writable.getWriter();
|
||||||
|
const reader = tds.readable.getReader();
|
||||||
|
|
||||||
|
// 'hello' in UTF-8 bytes
|
||||||
|
const bytes = new Uint8Array([104, 101, 108, 108, 111]);
|
||||||
|
await writer.write(bytes);
|
||||||
|
await writer.close();
|
||||||
|
|
||||||
|
const result = await reader.read();
|
||||||
|
testing.expectEqual(false, result.done);
|
||||||
|
testing.expectEqual('hello', result.value);
|
||||||
|
|
||||||
|
const result2 = await reader.read();
|
||||||
|
testing.expectEqual(true, result2.done);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
107
src/browser/webapi/encoding/TextDecoderStream.zig
Normal file
107
src/browser/webapi/encoding/TextDecoderStream.zig
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const js = @import("../../js/js.zig");
|
||||||
|
const Page = @import("../../Page.zig");
|
||||||
|
|
||||||
|
const ReadableStream = @import("../streams/ReadableStream.zig");
|
||||||
|
const WritableStream = @import("../streams/WritableStream.zig");
|
||||||
|
const TransformStream = @import("../streams/TransformStream.zig");
|
||||||
|
|
||||||
|
const TextDecoderStream = @This();
|
||||||
|
|
||||||
|
_transform: *TransformStream,
|
||||||
|
_fatal: bool,
|
||||||
|
_ignore_bom: bool,
|
||||||
|
|
||||||
|
const Label = enum {
|
||||||
|
utf8,
|
||||||
|
@"utf-8",
|
||||||
|
@"unicode-1-1-utf-8",
|
||||||
|
};
|
||||||
|
|
||||||
|
const InitOpts = struct {
|
||||||
|
fatal: bool = false,
|
||||||
|
ignoreBOM: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !TextDecoderStream {
|
||||||
|
if (label_) |label| {
|
||||||
|
_ = std.meta.stringToEnum(Label, label) orelse return error.RangeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts = opts_ orelse InitOpts{};
|
||||||
|
const transform = try TransformStream.initWithZigTransform(&decodeTransform, page);
|
||||||
|
return .{
|
||||||
|
._transform = transform,
|
||||||
|
._fatal = opts.fatal,
|
||||||
|
._ignore_bom = opts.ignoreBOM,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decodeTransform(controller: *TransformStream.DefaultController, chunk: js.Value) !void {
|
||||||
|
// chunk should be a Uint8Array; decode it as UTF-8 string
|
||||||
|
const typed_array = try chunk.toZig(js.TypedArray(u8));
|
||||||
|
var input = typed_array.values;
|
||||||
|
|
||||||
|
// Strip UTF-8 BOM if present
|
||||||
|
if (std.mem.startsWith(u8, input, &.{ 0xEF, 0xBB, 0xBF })) {
|
||||||
|
input = input[3..];
|
||||||
|
}
|
||||||
|
|
||||||
|
try controller.enqueue(.{ .string = input });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getReadable(self: *const TextDecoderStream) *ReadableStream {
|
||||||
|
return self._transform.getReadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getWritable(self: *const TextDecoderStream) *WritableStream {
|
||||||
|
return self._transform.getWritable();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFatal(self: *const TextDecoderStream) bool {
|
||||||
|
return self._fatal;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getIgnoreBOM(self: *const TextDecoderStream) bool {
|
||||||
|
return self._ignore_bom;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const JsApi = struct {
|
||||||
|
pub const bridge = js.Bridge(TextDecoderStream);
|
||||||
|
|
||||||
|
pub const Meta = struct {
|
||||||
|
pub const name = "TextDecoderStream";
|
||||||
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const constructor = bridge.constructor(TextDecoderStream.init, .{});
|
||||||
|
pub const encoding = bridge.property("utf-8", .{ .template = false });
|
||||||
|
pub const readable = bridge.accessor(TextDecoderStream.getReadable, null, .{});
|
||||||
|
pub const writable = bridge.accessor(TextDecoderStream.getWritable, null, .{});
|
||||||
|
pub const fatal = bridge.accessor(TextDecoderStream.getFatal, null, .{});
|
||||||
|
pub const ignoreBOM = bridge.accessor(TextDecoderStream.getIgnoreBOM, null, .{});
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../../testing.zig");
|
||||||
|
test "WebApi: TextDecoderStream" {
|
||||||
|
try testing.htmlRunner("streams/text_decoder_stream.html", .{});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user