Skip to content

Commit ed79b4e

Browse files
committed
FormData constructor form & submitter parameter
FormData takes two optional parameters: a form and a submitter. Building the FormData from these is a first step in supporting form submission. Basic extension of the HTMLForm element. There was more work done on the Select web api, because the netsurf implementation isn't great. But all of the input elements will need to have their web api extended.
1 parent f95defe commit ed79b4e

File tree

8 files changed

+600
-34
lines changed

8 files changed

+600
-34
lines changed

src/browser/env.zig

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ pub const SessionState = struct {
6262
// exists for the entire rendering of the page
6363
call_arena: std.mem.Allocator = undefined,
6464

65-
pub fn getNodeWrapper(self: *SessionState, comptime T: type, node: *parser.Node) !*T {
66-
if (parser.nodeGetEmbedderData(node)) |wrap| {
67-
return @alignCast(@ptrCast(wrap));
65+
pub fn getOrCreateNodeWrapper(self: *SessionState, comptime T: type, node: *parser.Node) !*T {
66+
if (try self.getNodeWrapper(T, node)) |wrap| {
67+
return wrap;
6868
}
6969

7070
const wrap = try self.arena.create(T);
@@ -73,4 +73,11 @@ pub const SessionState = struct {
7373
parser.nodeSetEmbedderData(node, wrap);
7474
return wrap;
7575
}
76+
77+
pub fn getNodeWrapper(_: *SessionState, comptime T: type, node: *parser.Node) !?*T {
78+
if (parser.nodeGetEmbedderData(node)) |wrap| {
79+
return @alignCast(@ptrCast(wrap));
80+
}
81+
return null;
82+
}
7683
};

src/browser/html/document.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ pub const HTMLDocument = struct {
187187
}
188188

189189
pub fn get_readyState(node: *parser.DocumentHTML, state: *SessionState) ![]const u8 {
190-
const self = try state.getNodeWrapper(HTMLDocument, @ptrCast(node));
190+
const self = try state.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(node));
191191
return @tagName(self.ready_state);
192192
}
193193

@@ -266,7 +266,7 @@ pub const HTMLDocument = struct {
266266
}
267267

268268
pub fn documentIsLoaded(html_doc: *parser.DocumentHTML, state: *SessionState) !void {
269-
const self = try state.getNodeWrapper(HTMLDocument, @ptrCast(html_doc));
269+
const self = try state.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc));
270270
self.ready_state = .interactive;
271271

272272
const evt = try parser.eventCreate();
@@ -277,7 +277,7 @@ pub const HTMLDocument = struct {
277277
}
278278

279279
pub fn documentIsComplete(html_doc: *parser.DocumentHTML, state: *SessionState) !void {
280-
const self = try state.getNodeWrapper(HTMLDocument, @ptrCast(html_doc));
280+
const self = try state.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc));
281281
self.ready_state = .complete;
282282
}
283283
};

src/browser/html/elements.zig

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ pub const Interfaces = .{
4747
HTMLEmbedElement,
4848
HTMLFieldSetElement,
4949
HTMLFontElement,
50-
HTMLFormElement,
5150
HTMLFrameElement,
5251
HTMLFrameSetElement,
5352
HTMLHRElement,
@@ -77,7 +76,6 @@ pub const Interfaces = .{
7776
HTMLProgressElement,
7877
HTMLQuoteElement,
7978
HTMLScriptElement,
80-
HTMLSelectElement,
8179
HTMLSourceElement,
8280
HTMLSpanElement,
8381
HTMLStyleElement,
@@ -95,6 +93,9 @@ pub const Interfaces = .{
9593
HTMLUListElement,
9694
HTMLVideoElement,
9795
CSSProperties,
96+
97+
@import("form.zig").HTMLFormElement,
98+
@import("select.zig").HTMLSelectElement,
9899
};
99100

100101
pub const Union = generate.Union(Interfaces);
@@ -516,12 +517,6 @@ pub const HTMLFontElement = struct {
516517
pub const subtype = .node;
517518
};
518519

519-
pub const HTMLFormElement = struct {
520-
pub const Self = parser.Form;
521-
pub const prototype = *HTMLElement;
522-
pub const subtype = .node;
523-
};
524-
525520
pub const HTMLFrameElement = struct {
526521
pub const Self = parser.Frame;
527522
pub const prototype = *HTMLElement;
@@ -806,12 +801,6 @@ pub const HTMLScriptElement = struct {
806801
}
807802
};
808803

809-
pub const HTMLSelectElement = struct {
810-
pub const Self = parser.Select;
811-
pub const prototype = *HTMLElement;
812-
pub const subtype = .node;
813-
};
814-
815804
pub const HTMLSourceElement = struct {
816805
pub const Self = parser.Source;
817806
pub const prototype = *HTMLElement;

src/browser/html/form.zig

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
const std = @import("std");
19+
const Allocator = std.mem.Allocator;
20+
21+
const parser = @import("../netsurf.zig");
22+
const HTMLElement = @import("elements.zig").HTMLElement;
23+
const FormData = @import("../xhr/form_data.zig").FormData;
24+
25+
pub const HTMLFormElement = struct {
26+
pub const Self = parser.Form;
27+
pub const prototype = *HTMLElement;
28+
pub const subtype = .node;
29+
30+
pub fn _requestSubmit(self: *parser.Form) !void {
31+
try parser.formElementSubmit(self);
32+
}
33+
34+
pub fn _reset(self: *parser.Form) !void {
35+
try parser.formElementReset(self);
36+
}
37+
};
38+
39+
pub const Submission = struct {
40+
method: ?[]const u8,
41+
form_data: FormData,
42+
};
43+
44+
pub fn processSubmission(arena: Allocator, form: *parser.Form) !?Submission {
45+
const form_element: *parser.Element = @ptrCast(form);
46+
const method = try parser.elementGetAttribute(form_element, "method");
47+
48+
return .{
49+
.method = method,
50+
.form_data = try FormData.fromForm(arena, form),
51+
};
52+
}
53+
54+
// Check xhr/form_data.zig for tests

src/browser/html/select.zig

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
const std = @import("std");
19+
20+
const parser = @import("../netsurf.zig");
21+
const HTMLElement = @import("elements.zig").HTMLElement;
22+
const SessionState = @import("../env.zig").SessionState;
23+
24+
pub const HTMLSelectElement = struct {
25+
pub const Self = parser.Select;
26+
pub const prototype = *HTMLElement;
27+
pub const subtype = .node;
28+
29+
// By default, if no option is explicitly selected, the first option should
30+
// be selected. However, libdom doesn't do this, and it sets the
31+
// selectedIndex to -1, which is a valid value for "nothing selected".
32+
// Therefore, when libdom says the selectedIndex == -1, we don't know if
33+
// it means that nothing is selected, or if the first option is selected by
34+
// default.
35+
// There are cases where this won't work, but when selectedIndex is
36+
// explicitly set, we set this boolean flag. Then, when we're getting then
37+
// selectedIndex, if this flag is == false, which is to say that if
38+
// selectedIndex hasn't been explicitly set AND if we have at least 1 option
39+
// AND if it isn't a multi select, we can make the 1st item selected by
40+
// default (by returning selectedIndex == 0).
41+
explicit_index_set: bool = false,
42+
43+
pub fn get_length(select: *parser.Select) !u32 {
44+
return parser.selectGetLength(select);
45+
}
46+
47+
pub fn get_form(select: *parser.Select) !?*parser.Form {
48+
return parser.selectGetForm(select);
49+
}
50+
51+
pub fn get_name(select: *parser.Select) ![]const u8 {
52+
return parser.selectGetName(select);
53+
}
54+
pub fn set_name(select: *parser.Select, name: []const u8) !void {
55+
return parser.selectSetName(select, name);
56+
}
57+
58+
pub fn get_disabled(select: *parser.Select) !bool {
59+
return parser.selectGetDisabled(select);
60+
}
61+
pub fn set_disabled(select: *parser.Select, disabled: bool) !void {
62+
return parser.selectSetDisabled(select, disabled);
63+
}
64+
65+
pub fn get_multiple(select: *parser.Select) !bool {
66+
return parser.selectGetMultiple(select);
67+
}
68+
pub fn set_multiple(select: *parser.Select, multiple: bool) !void {
69+
return parser.selectSetMultiple(select, multiple);
70+
}
71+
72+
pub fn get_selectedIndex(select: *parser.Select, state: *SessionState) !i32 {
73+
const self = try state.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select));
74+
const selected_index = try parser.selectGetSelectedIndex(select);
75+
76+
// See the explicit_index_set field documentation
77+
if (!self.explicit_index_set) {
78+
if (selected_index == -1) {
79+
if (try parser.selectGetMultiple(select) == false) {
80+
if (try get_length(select) > 0) {
81+
return 0;
82+
}
83+
}
84+
}
85+
}
86+
return selected_index;
87+
}
88+
89+
// Libdom's dom_html_select_select_set_selected_index will crash if index
90+
// is out of range, and it doesn't properly unset options
91+
pub fn set_selectedIndex(select: *parser.Select, index: i32, state: *SessionState) !void {
92+
var self = try state.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select));
93+
self.explicit_index_set = true;
94+
95+
const options = try parser.selectGetOptions(select);
96+
const len = try parser.optionCollectionGetLength(options);
97+
for (0..len) |i| {
98+
const option = try parser.optionCollectionItem(options, @intCast(i));
99+
try parser.optionSetSelected(option, false);
100+
}
101+
if (index >= 0 and index < try get_length(select)) {
102+
const option = try parser.optionCollectionItem(options, @intCast(index));
103+
try parser.optionSetSelected(option, true);
104+
}
105+
}
106+
};
107+
108+
const testing = @import("../../testing.zig");
109+
test "Browser.HTML.Select" {
110+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html =
111+
\\ <form id=f1>
112+
\\ <select id=s1 name=s1><option>o1<option>o2</select>
113+
\\ </form>
114+
\\ <select id=s2></select>
115+
});
116+
defer runner.deinit();
117+
118+
try runner.testCases(&.{
119+
.{ "const s = document.getElementById('s1');", null },
120+
.{ "s.form", "[object HTMLFormElement]" },
121+
122+
.{ "document.getElementById('s2').form", "null" },
123+
124+
.{ "s.disabled", "false" },
125+
.{ "s.disabled = true", null },
126+
.{ "s.disabled", "true" },
127+
.{ "s.disabled = false", null },
128+
.{ "s.disabled", "false" },
129+
130+
.{ "s.multiple", "false" },
131+
.{ "s.multiple = true", null },
132+
.{ "s.multiple", "true" },
133+
.{ "s.multiple = false", null },
134+
.{ "s.multiple", "false" },
135+
136+
.{ "s.name;", "s1" },
137+
.{ "s.name = 'sel1';", null },
138+
.{ "s.name", "sel1" },
139+
140+
.{ "s.length;", "2" },
141+
142+
.{ "s.selectedIndex", "0" },
143+
.{ "s.selectedIndex = 2", null }, // out of range
144+
.{ "s.selectedIndex", "-1" },
145+
146+
.{ "s.selectedIndex = -1", null },
147+
.{ "s.selectedIndex", "-1" },
148+
149+
.{ "s.selectedIndex = 0", null },
150+
.{ "s.selectedIndex", "0" },
151+
152+
.{ "s.selectedIndex = 1", null },
153+
.{ "s.selectedIndex", "1" },
154+
155+
.{ "s.selectedIndex = -323", null },
156+
.{ "s.selectedIndex", "-1" },
157+
}, .{});
158+
}

0 commit comments

Comments
 (0)