Skip to content

Commit 618b28a

Browse files
committed
add FormData and base KeyValueList
1 parent c966211 commit 618b28a

File tree

5 files changed

+325
-87
lines changed

5 files changed

+325
-87
lines changed

src/browser/js/bridge.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ pub const JsApis = flattenTypes(&.{
444444
@import("../webapi/css/CSSStyleProperties.zig"),
445445
@import("../webapi/Document.zig"),
446446
@import("../webapi/HTMLDocument.zig"),
447+
@import("../webapi/KeyValueList.zig"),
447448
@import("../webapi/DocumentFragment.zig"),
448449
@import("../webapi/DOMException.zig"),
449450
@import("../webapi/DOMTreeWalker.zig"),
@@ -489,6 +490,7 @@ pub const JsApis = flattenTypes(&.{
489490
@import("../webapi/EventTarget.zig"),
490491
@import("../webapi/Location.zig"),
491492
@import("../webapi/Navigator.zig"),
493+
@import("../webapi/net/FormData.zig"),
492494
@import("../webapi/net/Request.zig"),
493495
@import("../webapi/net/Response.zig"),
494496
@import("../webapi/net/URLSearchParams.zig"),
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<script src="../testing.js"></script>
3+
4+
<div id=main></div>
5+
6+
<script id=webcomponents>
7+
class LightPanda extends HTMLElement {
8+
constructor() {
9+
super();
10+
}
11+
connectedCallback() {
12+
this.append('connected');
13+
}
14+
}
15+
16+
window.customElements.define("lightpanda-test", LightPanda);
17+
const main = document.getElementById('main');
18+
main.appendChild(document.createElement('lightpanda-test'));
19+
20+
testing.expectEqual('<lightpanda-test>connected</lightpanda-test>', main.innerHTML)
21+
testing.expectEqual('[object DataSet]', document.createElement('lightpanda-test').dataset.toString());
22+
testing.expectEqual('[object DataSet]', document.createElement('lightpanda-test.x').dataset.toString());
23+
</script>
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
const std = @import("std");
2+
3+
const String = @import("../../string.zig").String;
4+
5+
const js = @import("../js/js.zig");
6+
const Page = @import("../Page.zig");
7+
8+
const Allocator = std.mem.Allocator;
9+
10+
pub fn registerTypes() []const type {
11+
return &.{
12+
KeyIterator,
13+
ValueIterator,
14+
EntryIterator,
15+
};
16+
}
17+
18+
pub const KeyValueList = @This();
19+
20+
_entries: std.ArrayListUnmanaged(Entry) = .empty,
21+
22+
pub const empty: KeyValueList = .{
23+
._entries = .empty,
24+
};
25+
26+
pub const Entry = struct {
27+
name: String,
28+
value: String,
29+
};
30+
31+
pub fn init() KeyValueList {
32+
return .{};
33+
}
34+
35+
pub fn ensureTotalCapacity(self: *KeyValueList, allocator: Allocator, n: usize) !void {
36+
return self._entries.ensureTotalCapacity(allocator, n);
37+
}
38+
39+
pub fn get(self: *const KeyValueList, name: []const u8) ?[]const u8 {
40+
for (self._entries.items) |*entry| {
41+
if (entry.name.eqlSlice(name)) {
42+
return entry.value.str();
43+
}
44+
}
45+
return null;
46+
}
47+
48+
pub fn getAll(self: *const KeyValueList, name: []const u8, page: *Page) ![]const []const u8 {
49+
const arena = page.call_arena;
50+
var arr: std.ArrayList([]const u8) = .empty;
51+
for (self._entries.items) |*entry| {
52+
if (entry.name.eqlSlice(name)) {
53+
try arr.append(arena, entry.value.str());
54+
}
55+
}
56+
return arr.items;
57+
}
58+
59+
pub fn has(self: *const KeyValueList, name: []const u8) bool {
60+
for (self._entries.items) |*entry| {
61+
if (entry.name.eqlSlice(name)) {
62+
return true;
63+
}
64+
}
65+
return false;
66+
}
67+
68+
pub fn append(self: *KeyValueList, allocator: Allocator, name: []const u8, value: []const u8) !void {
69+
try self._entries.append(allocator, .{
70+
.name = try String.init(allocator, name, .{}),
71+
.value = try String.init(allocator, value, .{}),
72+
});
73+
}
74+
75+
pub fn appendAssumeCapacity(self: *KeyValueList, allocator: Allocator, name: []const u8, value: []const u8) !void {
76+
self._entries.appendAssumeCapacity(.{
77+
.name = try String.init(allocator, name, .{}),
78+
.value = try String.init(allocator, value, .{}),
79+
});
80+
}
81+
82+
pub fn delete(self: *KeyValueList, name: []const u8, value: ?[]const u8) void {
83+
var i: usize = 0;
84+
while (i < self._entries.items.len) {
85+
const entry = self._entries.items[i];
86+
if (entry.name.eqlSlice(name)) {
87+
if (value == null or entry.value.eqlSlice(value.?)) {
88+
_ = self._entries.swapRemove(i);
89+
continue;
90+
}
91+
}
92+
i += 1;
93+
}
94+
}
95+
96+
pub fn set(self: *KeyValueList, allocator: Allocator, name: []const u8, value: []const u8) !void {
97+
self.delete(name, null);
98+
try self.append(allocator, name, value);
99+
}
100+
101+
pub fn len(self: *const KeyValueList) usize {
102+
return self._entries.items.len;
103+
}
104+
105+
pub fn items(self: *const KeyValueList) []const Entry {
106+
return self._entries.items;
107+
}
108+
109+
pub const Iterator = struct {
110+
index: u32 = 0,
111+
kv: *KeyValueList,
112+
113+
// Why? Because whenever an Iterator is created, we need to increment the
114+
// RC of what it's iterating. And when the iterator is destroyed, we need
115+
// to decrement it. The generic iterator which will wrap this handles that
116+
// by using this "list" field. Most things that use the GenericIterator can
117+
// just set `list: *ZigCollection`, and everything will work. But KeyValueList
118+
// is being composed by various types, so it can't reference those types.
119+
// Using *anyopaque here is "dangerous", in that it requires the composer
120+
// to pass the right value, which normally would be itself (`*Self`), but
121+
// only because (as of now) everyting that uses KeyValueList has no prototype
122+
list: *anyopaque,
123+
124+
pub const Entry = struct { []const u8, []const u8 };
125+
126+
pub fn next(self: *Iterator, _: *const Page) ?Iterator.Entry {
127+
const index = self.index;
128+
const entries = self.kv._entries.items;
129+
if (index >= entries.len) {
130+
return null;
131+
}
132+
self.index = index + 1;
133+
134+
const e = &entries[index];
135+
return .{ e.name.str(), e.value.str() };
136+
}
137+
};
138+
139+
pub fn iterator(self: *const KeyValueList) Iterator {
140+
return .{ .list = self };
141+
}
142+
143+
const GenericIterator = @import("collections/iterator.zig").Entry;
144+
pub const KeyIterator = GenericIterator(Iterator, "0");
145+
pub const ValueIterator = GenericIterator(Iterator, "1");
146+
pub const EntryIterator = GenericIterator(Iterator, null);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
const std = @import("std");
2+
3+
const log = @import("../../../log.zig");
4+
5+
const js = @import("../../js/js.zig");
6+
const Page = @import("../../Page.zig");
7+
const KeyValueList = @import("../KeyValueList.zig");
8+
9+
const Alloctor = std.mem.Allocator;
10+
11+
const FormData = @This();
12+
13+
_arena: Alloctor,
14+
_list: KeyValueList,
15+
16+
pub fn init(page: *Page) !*FormData {
17+
return page._factory.create(FormData{
18+
._arena = page.arena,
19+
._list = KeyValueList.init(),
20+
});
21+
}
22+
23+
pub fn get(self: *const FormData, name: []const u8) ?[]const u8 {
24+
return self._list.get(name);
25+
}
26+
27+
pub fn getAll(self: *const FormData, name: []const u8, page: *Page) ![]const []const u8 {
28+
return self._list.getAll(name, page);
29+
}
30+
31+
pub fn has(self: *const FormData, name: []const u8) bool {
32+
return self._list.has(name);
33+
}
34+
35+
pub fn set(self: *FormData, name: []const u8, value: []const u8) !void {
36+
return self._list.set(self._arena, name, value);
37+
}
38+
39+
pub fn append(self: *FormData, name: []const u8, value: []const u8) !void {
40+
return self._list.append(self._arena, name, value);
41+
}
42+
43+
pub fn delete(self: *FormData, name: []const u8) void {
44+
self._list.delete(name, null);
45+
}
46+
47+
pub fn keys(self: *FormData, page: *Page) !*KeyValueList.KeyIterator {
48+
return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, page);
49+
}
50+
51+
pub fn values(self: *FormData, page: *Page) !*KeyValueList.ValueIterator {
52+
return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, page);
53+
}
54+
55+
pub fn entries(self: *FormData, page: *Page) !*KeyValueList.EntryIterator {
56+
return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, page);
57+
}
58+
59+
pub fn forEach(self: *FormData, cb_: js.Function, js_this_: ?js.Object) !void {
60+
const cb = if (js_this_) |js_this| try cb_.withThis(js_this) else cb_;
61+
62+
for (self._list._entries.items) |entry| {
63+
cb.call(void, .{ entry.value.str(), entry.name.str(), self }) catch |err| {
64+
// this is a non-JS error
65+
log.warn(.js, "FormData.forEach", .{ .err = err });
66+
};
67+
}
68+
}
69+
70+
pub const Iterator = struct {
71+
index: u32 = 0,
72+
list: *const FormData,
73+
74+
const Entry = struct { []const u8, []const u8 };
75+
76+
pub fn next(self: *Iterator, _: *Page) !?Iterator.Entry {
77+
const index = self.index;
78+
const items = self.list._list.items();
79+
if (index >= items.len) {
80+
return null;
81+
}
82+
self.index = index + 1;
83+
84+
const e = &items[index];
85+
return .{ e.name.str(), e.value.str() };
86+
}
87+
};
88+
89+
pub const JsApi = struct {
90+
pub const bridge = js.Bridge(FormData);
91+
92+
pub const Meta = struct {
93+
pub const name = "FormData";
94+
pub const prototype_chain = bridge.prototypeChain();
95+
pub var class_index: u16 = 0;
96+
};
97+
98+
pub const constructor = bridge.constructor(FormData.init, .{});
99+
pub const has = bridge.function(FormData.has, .{});
100+
pub const get = bridge.function(FormData.get, .{});
101+
pub const set = bridge.function(FormData.set, .{});
102+
pub const append = bridge.function(FormData.append, .{});
103+
pub const getAll = bridge.function(FormData.getAll, .{});
104+
pub const delete = bridge.function(FormData.delete, .{});
105+
pub const keys = bridge.function(FormData.keys, .{});
106+
pub const values = bridge.function(FormData.values, .{});
107+
pub const entries = bridge.function(FormData.entries, .{});
108+
pub const symbol_iterator = bridge.iterator(FormData.entries, .{});
109+
pub const forEach = bridge.function(FormData.forEach, .{});
110+
};
111+
112+
const testing = @import("../../../testing.zig");
113+
test "WebApi: FormData" {
114+
try testing.htmlRunner("net/form_data.html", .{});
115+
}

0 commit comments

Comments
 (0)