mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-30 15:41:48 +00:00 
			
		
		
		
	more Headers compatibility
This commit is contained in:
		| @@ -23,39 +23,68 @@ const Page = @import("../page.zig").Page; | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/Headers | ||||
| const Headers = @This(); | ||||
|  | ||||
| headers: std.StringHashMapUnmanaged([]const u8), | ||||
| // Case-Insensitive String HashMap. | ||||
| // This allows us to avoid having to allocate lowercase keys all the time. | ||||
| const HeaderHashMap = std.HashMapUnmanaged([]const u8, []const u8, struct { | ||||
|     pub fn hash(_: @This(), s: []const u8) u64 { | ||||
|         var hasher = std.hash.Wyhash.init(s.len); | ||||
|         for (s) |c| { | ||||
|             hasher.update(&.{std.ascii.toLower(c)}); | ||||
|         } | ||||
|  | ||||
|         return hasher.final(); | ||||
|     } | ||||
|     pub fn eql(_: @This(), a: []const u8, b: []const u8) bool { | ||||
|         if (a.len != b.len) return false; | ||||
|  | ||||
|         for (a, b) |c1, c2| { | ||||
|             if (std.ascii.toLower(c1) != std.ascii.toLower(c2)) return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
| }, 80); | ||||
|  | ||||
| headers: HeaderHashMap = .empty, | ||||
|  | ||||
| // They can either be: | ||||
| // | ||||
| // 1. An array of string pairs. | ||||
| // 2. An object with string keys to string values. | ||||
| // 3. Another Headers object. | ||||
| const HeadersInit = union(enum) { | ||||
|     strings: []const []const u8, | ||||
|     // headers: Headers, | ||||
| pub const HeadersInit = union(enum) { | ||||
|     // List of Pairs of []const u8 | ||||
|     strings: []const []const []const u8, | ||||
|     headers: *Headers, | ||||
| }; | ||||
|  | ||||
| pub fn constructor(_init: ?[]const HeadersInit, page: *Page) !Headers { | ||||
| pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers { | ||||
|     const arena = page.arena; | ||||
|     var headers = std.StringHashMapUnmanaged([]const u8).empty; | ||||
|     var headers: HeaderHashMap = .empty; | ||||
|  | ||||
|     if (_init) |init| { | ||||
|         for (init) |item| { | ||||
|             switch (item) { | ||||
|                 .strings => |pair| { | ||||
|         switch (init) { | ||||
|             .strings => |kvs| { | ||||
|                 for (kvs) |pair| { | ||||
|                     // Can only have two string elements if in a pair. | ||||
|                     if (pair.len != 2) { | ||||
|                         return error.TypeError; | ||||
|                     } | ||||
|  | ||||
|                     const raw_key = pair[0]; | ||||
|                     const value = pair[1]; | ||||
|                     const key = try std.ascii.allocLowerString(arena, raw_key); | ||||
|                     const key = try page.arena.dupe(u8, pair[0]); | ||||
|                     const value = try page.arena.dupe(u8, pair[1]); | ||||
|  | ||||
|                     try headers.put(arena, key, value); | ||||
|                 }, | ||||
|                 // .headers => |_| {}, | ||||
|             } | ||||
|                 } | ||||
|             }, | ||||
|             .headers => |hdrs| { | ||||
|                 var iter = hdrs.headers.iterator(); | ||||
|                 while (iter.next()) |entry| { | ||||
|                     const key = try page.arena.dupe(u8, entry.key_ptr.*); | ||||
|                     const value = try page.arena.dupe(u8, entry.value_ptr.*); | ||||
|                     try headers.put(arena, key, value); | ||||
|                 } | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -64,14 +93,70 @@ pub fn constructor(_init: ?[]const HeadersInit, page: *Page) !Headers { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| pub fn _get(self: *const Headers, header: []const u8, page: *Page) !?[]const u8 { | ||||
|     const arena = page.arena; | ||||
|     const key = try std.ascii.allocLowerString(arena, header); | ||||
| pub fn clone(self: *Headers, allocator: std.mem.Allocator) !Headers { | ||||
|     return Headers{ | ||||
|         .headers = try self.headers.clone(allocator), | ||||
|     }; | ||||
| } | ||||
|  | ||||
|     const value = (self.headers.getEntry(key) orelse return null).value_ptr.*; | ||||
| pub fn _append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void { | ||||
|     const arena = page.arena; | ||||
|  | ||||
|     if (self.headers.getEntry(name)) |entry| { | ||||
|         // If we found it, append the value. | ||||
|         const new_value = try std.fmt.allocPrint(arena, "{s}, {s}", .{ entry.value_ptr.*, value }); | ||||
|         entry.value_ptr.* = new_value; | ||||
|     } else { | ||||
|         // Otherwise, we should just put it in. | ||||
|         try self.headers.putNoClobber( | ||||
|             arena, | ||||
|             try arena.dupe(u8, name), | ||||
|             try arena.dupe(u8, value), | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn _delete(self: *Headers, name: []const u8) void { | ||||
|     _ = self.headers.remove(name); | ||||
| } | ||||
|  | ||||
| // TODO: entries iterator | ||||
| // They should be: | ||||
| // 1. Sorted in lexicographical order. | ||||
| // 2. Duplicate header names should be combined. | ||||
|  | ||||
| // TODO: header for each | ||||
|  | ||||
| pub fn _get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 { | ||||
|     const arena = page.arena; | ||||
|     const value = (self.headers.getEntry(name) orelse return null).value_ptr.*; | ||||
|     return try arena.dupe(u8, value); | ||||
| } | ||||
|  | ||||
| pub fn _has(self: *const Headers, name: []const u8) bool { | ||||
|     return self.headers.contains(name); | ||||
| } | ||||
|  | ||||
| // TODO: keys iterator | ||||
|  | ||||
| pub fn _set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void { | ||||
|     const arena = page.arena; | ||||
|  | ||||
|     if (self.headers.getEntry(name)) |entry| { | ||||
|         // If we found it, set the value. | ||||
|         entry.value_ptr.* = try arena.dupe(u8, value); | ||||
|     } else { | ||||
|         // Otherwise, we should just put it in. | ||||
|         try self.headers.putNoClobber( | ||||
|             arena, | ||||
|             try arena.dupe(u8, name), | ||||
|             try arena.dupe(u8, value), | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // TODO: values iterator | ||||
|  | ||||
| const testing = @import("../../testing.zig"); | ||||
| test "fetch: headers" { | ||||
|     var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" }); | ||||
| @@ -85,4 +170,19 @@ test "fetch: headers" { | ||||
|         .{ "let headers = new Headers([['Set-Cookie', 'name=world']])", "undefined" }, | ||||
|         .{ "headers.get('set-cookie')", "name=world" }, | ||||
|     }, .{}); | ||||
|  | ||||
|     // adapted from the mdn examples | ||||
|     try runner.testCases(&.{ | ||||
|         .{ "const myHeaders = new Headers();", "undefined" }, | ||||
|         .{ "myHeaders.append('Content-Type', 'image/jpeg')", "undefined" }, | ||||
|         .{ "myHeaders.has('Picture-Type')", "false" }, | ||||
|         .{ "myHeaders.get('Content-Type')", "image/jpeg" }, | ||||
|         .{ "myHeaders.append('Content-Type', 'image/png')", "undefined" }, | ||||
|         .{ "myHeaders.get('Content-Type')", "image/jpeg, image/png" }, | ||||
|         .{ "myHeaders.delete('Content-Type')", "undefined" }, | ||||
|         .{ "myHeaders.get('Content-Type')", "null" }, | ||||
|         .{ "myHeaders.set('Picture-Type', 'image/svg')", "undefined" }, | ||||
|         .{ "myHeaders.get('Picture-Type')", "image/svg" }, | ||||
|         .{ "myHeaders.has('Picture-Type')", "true" }, | ||||
|     }, .{}); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Muki Kiboigo
					Muki Kiboigo