From a7e3bea672eeb2efb68d3af78d2a86310dfd56c8 Mon Sep 17 00:00:00 2001 From: Armaan Sandhu Date: Sun, 24 May 2026 13:04:33 +0530 Subject: [PATCH] feat(webapi): implement W3C File API --- src/browser/tests/file.html | 50 +++++++++++++++++++++++++++---- src/browser/webapi/Blob.zig | 4 +-- src/browser/webapi/File.zig | 60 +++++++++++++++++++++++++++++++++---- 3 files changed, 102 insertions(+), 12 deletions(-) diff --git a/src/browser/tests/file.html b/src/browser/tests/file.html index 3db5fdfe..0b301b79 100644 --- a/src/browser/tests/file.html +++ b/src/browser/tests/file.html @@ -1,12 +1,52 @@ - Test Document Title + Test File Web API - + + + + diff --git a/src/browser/webapi/Blob.zig b/src/browser/webapi/Blob.zig index 2bead21d..9cc0b4f6 100644 --- a/src/browser/webapi/Blob.zig +++ b/src/browser/webapi/Blob.zig @@ -113,7 +113,7 @@ pub fn initFromBytes(data: []const u8, content_type: []const u8, validate_mime: } /// Validates and normalizes MIME type according to spec. -fn validateMimeType(arena: Allocator, mime_type: []const u8, full_validation: bool) ![]const u8 { +pub fn validateMimeType(arena: Allocator, mime_type: []const u8, full_validation: bool) ![]const u8 { if (mime_type.len == 0) { return ""; } @@ -171,7 +171,7 @@ const vector_sizes = blk: { }; /// Writes a single part with optional line ending normalization. -fn writePartWithEndings(part: []const u8, use_native_endings: bool, writer: *Writer) !void { +pub fn writePartWithEndings(part: []const u8, use_native_endings: bool, writer: *Writer) !void { // Transparent - no conversion needed. if (!use_native_endings) { try writer.writeAll(part); diff --git a/src/browser/webapi/File.zig b/src/browser/webapi/File.zig index a0dce126..a740ed04 100644 --- a/src/browser/webapi/File.zig +++ b/src/browser/webapi/File.zig @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire @@ -27,13 +27,61 @@ const Blob = @import("Blob.zig"); const File = @This(); _proto: *Blob, +_name: []const u8, +_last_modified: i64, -// TODO: Implement File API. -pub fn init(page: *Page) !*File { +pub const InitOptions = struct { + type: []const u8 = "", + endings: []const u8 = "transparent", + lastModified: ?i64 = null, +}; + +pub fn init( + parts_: ?[]const js.Value, + name_: []const u8, + opts_: ?InitOptions, + page: *Page, +) !*File { const session = page.session; - const arena = try session.getArena(.tiny, "File"); + const arena = try session.getArena(.large, "File"); errdefer session.releaseArena(arena); - return page.factory.blob(arena, File{ ._proto = undefined }); + + const opts = opts_ orelse InitOptions{}; + const mime = try Blob.validateMimeType(arena, opts.type, false); + + const data = blk: { + if (parts_) |blob_parts| { + const use_native_endings = std.mem.eql(u8, opts.endings, "native"); + var w: std.Io.Writer.Allocating = .init(arena); + for (blob_parts) |js_val| { + const part = try js_val.toStringSmart(); + try Blob.writePartWithEndings(part, use_native_endings, &w.writer); + } + break :blk w.written(); + } + + break :blk ""; + }; + + const last_modified = opts.lastModified orelse std.time.milliTimestamp(); + + const file = try page.factory.blob(arena, File{ + ._proto = undefined, + ._name = try arena.dupe(u8, name_), + ._last_modified = last_modified, + }); + file._proto._slice = data; + file._proto._mime = mime; + + return file; +} + +pub fn getName(self: *const File) []const u8 { + return self._name; +} + +pub fn getLastModified(self: *const File) f64 { + return @floatFromInt(self._last_modified); } pub const JsApi = struct { @@ -46,6 +94,8 @@ pub const JsApi = struct { }; pub const constructor = bridge.constructor(File.init, .{}); + pub const name = bridge.accessor(File.getName, null, .{}); + pub const lastModified = bridge.accessor(File.getLastModified, null, .{}); }; const testing = @import("../../testing.zig");