1use crate::dom::Dom;
14use crate::event_detail::{FormEnctype, FormMethod, SubmitDetail};
15use crate::input_type::InputTypeState;
16use crate::node_id::NodeId;
17
18fn is_listed(tag: &str) -> bool {
21 matches!(
22 tag,
23 "button" | "fieldset" | "input" | "object" | "output" | "select" | "textarea"
24 )
25}
26
27impl<Ext> Dom<Ext> {
28 pub fn form_owner(&self, id: NodeId) -> Option<NodeId> {
39 let tag = self.get_node(id)?.tag_name()?;
40 let mut ancestor = self.parent_element_id(id);
41 while let Some(a) = ancestor {
42 if self.tag_is(a, "form") {
43 break;
44 }
45 ancestor = self.parent_element_id(a);
46 }
47 let connected = self.root_of(id) == Some(self.root());
48 self.owner_given_ancestor(id, tag, ancestor, connected)
49 }
50
51 pub fn form_listed_elements(&self, form: NodeId) -> Vec<NodeId> {
56 let mut out = Vec::new();
57 if !self.tag_is(form, "form") {
58 return out;
59 }
60 let Some(root) = self.root_of(form) else {
61 return out;
62 };
63 let connected = root == self.root();
64 let mut stack: Vec<(NodeId, Option<NodeId>)> = vec![(root, None)];
66 while let Some((id, ancestor_form)) = stack.pop() {
67 let Some(node) = self.get_node(id) else {
68 continue;
69 };
70 let mut child_ancestor = ancestor_form;
71 if let Some(tag) = node.tag_name() {
72 if is_listed(tag)
73 && self.owner_given_ancestor(id, tag, ancestor_form, connected) == Some(form)
74 {
75 out.push(id);
76 }
77 if tag == "form" {
78 child_ancestor = Some(id);
79 }
80 }
81 let first = stack.len();
83 let mut c = node.first_child;
84 while let Some(cid) = c {
85 stack.push((cid, child_ancestor));
86 c = self.get_node(cid).and_then(|n| n.next_sibling);
87 }
88 stack[first..].reverse();
89 }
90 out
91 }
92
93 pub fn is_submit_button(&self, id: NodeId) -> bool {
98 match self.get_node(id).and_then(|n| n.tag_name()) {
99 Some("button") => !self.get_attribute(id, "type").is_some_and(|t| {
100 t.eq_ignore_ascii_case("reset") || t.eq_ignore_ascii_case("button")
101 }),
102 Some("input") => matches!(
103 self.input_type_state(id),
104 Some(InputTypeState::Submit | InputTypeState::Image)
105 ),
106 _ => false,
107 }
108 }
109
110 pub fn submit_detail(&self, form: NodeId, submitter: Option<NodeId>) -> SubmitDetail {
120 let button = submitter.filter(|&s| self.is_submit_button(s));
121 let attr = |over: &str, own: &str| -> Option<&str> {
122 button
123 .and_then(|b| self.get_attribute(b, over))
124 .or_else(|| self.get_attribute(form, own))
125 };
126 let mut d = SubmitDetail::new(submitter);
127 d.action = attr("formaction", "action").unwrap_or("").to_string();
128 d.method = attr("formmethod", "method")
129 .map(FormMethod::from_attribute)
130 .unwrap_or_default();
131 d.enctype = attr("formenctype", "enctype")
132 .map(FormEnctype::from_attribute)
133 .unwrap_or_default();
134 d.target = attr("formtarget", "target").unwrap_or("").to_string();
135 d.no_validate = button.is_some_and(|b| self.has_attribute(b, "formnovalidate"))
136 || self.has_attribute(form, "novalidate");
137 d
138 }
139
140 fn owner_given_ancestor(
143 &self,
144 id: NodeId,
145 tag: &str,
146 ancestor_form: Option<NodeId>,
147 connected: bool,
148 ) -> Option<NodeId> {
149 if is_listed(tag)
150 && connected
151 && let Some(form_id) = self.get_attribute(id, "form")
152 {
153 return self
157 .get_element_by_id(form_id)
158 .filter(|&f| self.tag_is(f, "form") && self.root_of(f) == Some(self.root()));
159 }
160 ancestor_form
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use crate::node_id::NodeId;
167 use crate::{Dom, FormEnctype, FormMethod};
168
169 fn el(dom: &mut Dom, parent: NodeId, tag: &str, attrs: &[(&str, &str)]) -> NodeId {
170 let e = dom.create_element(tag);
171 for (k, v) in attrs {
172 dom.set_attribute(e, k, v).unwrap();
173 }
174 dom.append_child(parent, e).unwrap();
175 e
176 }
177
178 #[test]
179 fn owner_is_the_nearest_ancestor_form_without_a_form_attribute() {
180 let mut dom: Dom = Dom::new();
181 let root = dom.root();
182 let form = el(&mut dom, root, "form", &[]);
183 let div = el(&mut dom, form, "div", &[]);
184 let input = el(&mut dom, div, "input", &[]);
185 let outside = el(&mut dom, root, "input", &[]);
186 assert_eq!(dom.form_owner(input), Some(form));
187 assert_eq!(dom.form_owner(outside), None);
188 assert_eq!(dom.form_owner(form), None, "a form is not its own owner");
189 }
190
191 #[test]
194 fn form_attribute_names_the_owner() {
195 let mut dom: Dom = Dom::new();
196 let root = dom.root();
197 let a = el(&mut dom, root, "form", &[("id", "a")]);
198 let b = el(&mut dom, root, "form", &[("id", "b")]);
199 let in_a_for_b = el(&mut dom, a, "input", &[("form", "b")]);
200 let outside_for_a = el(&mut dom, root, "select", &[("form", "a")]);
201 assert_eq!(dom.form_owner(in_a_for_b), Some(b));
202 assert_eq!(dom.form_owner(outside_for_a), Some(a));
203 }
204
205 #[test]
209 fn form_attribute_that_names_no_form_means_no_owner() {
210 let mut dom: Dom = Dom::new();
211 let root = dom.root();
212 let form = el(&mut dom, root, "form", &[("id", "f")]);
213 el(&mut dom, root, "div", &[("id", "d")]);
214 let unknown = el(&mut dom, form, "input", &[("form", "nope")]);
215 let div_id = el(&mut dom, form, "button", &[("form", "d")]);
216 let empty = el(&mut dom, form, "textarea", &[("form", "")]);
217 assert_eq!(dom.form_owner(unknown), None);
218 assert_eq!(dom.form_owner(div_id), None);
219 assert_eq!(dom.form_owner(empty), None);
220 }
221
222 #[test]
225 fn form_attribute_ignored_on_non_listed_and_disconnected_elements() {
226 let mut dom: Dom = Dom::new();
227 let root = dom.root();
228 let f = el(&mut dom, root, "form", &[("id", "f")]);
229 let g = el(&mut dom, root, "form", &[("id", "g")]);
230 let label = el(&mut dom, g, "label", &[("form", "f")]);
231 assert_eq!(dom.form_owner(label), Some(g));
232
233 let detached = dom.create_element("form");
234 let input = dom.create_element("input");
235 dom.set_attribute(input, "form", "f").unwrap();
236 dom.append_child(detached, input).unwrap();
237 assert_eq!(dom.form_owner(input), Some(detached));
238 let _ = f;
239 }
240
241 #[test]
245 fn listed_elements_follow_document_tree_order() {
246 let mut dom: Dom = Dom::new();
247 let root = dom.root();
248 let before = el(&mut dom, root, "input", &[("form", "f")]);
249 let form = el(&mut dom, root, "form", &[("id", "f")]);
250 let fs = el(&mut dom, form, "fieldset", &[]);
251 let inner = el(&mut dom, fs, "input", &[]);
252 el(&mut dom, form, "input", &[("form", "other")]);
253 el(&mut dom, form, "div", &[]);
254 let after = el(&mut dom, root, "button", &[("form", "f")]);
255 el(&mut dom, root, "input", &[]);
256 assert_eq!(
257 dom.form_listed_elements(form),
258 vec![before, fs, inner, after]
259 );
260 }
261
262 #[test]
265 fn submit_button_type_matches_ascii_case_insensitively() {
266 let mut dom: Dom = Dom::new();
267 let root = dom.root();
268 let upper_submit_input = el(&mut dom, root, "input", &[("type", "SUBMIT")]);
269 let mixed_submit_input = el(&mut dom, root, "input", &[("type", "Submit")]);
270 let upper_reset = el(&mut dom, root, "button", &[("type", "RESET")]);
271 let mixed_button = el(&mut dom, root, "button", &[("type", "Button")]);
272 let upper_submit_button = el(&mut dom, root, "button", &[("type", "SUBMIT")]);
273 assert!(dom.is_submit_button(upper_submit_input));
274 assert!(dom.is_submit_button(mixed_submit_input));
275 assert!(!dom.is_submit_button(upper_reset));
276 assert!(!dom.is_submit_button(mixed_button));
277 assert!(dom.is_submit_button(upper_submit_button));
278 }
279
280 #[test]
283 fn an_image_input_is_a_submit_button() {
284 let mut dom: Dom = Dom::new();
285 let root = dom.root();
286 let form = el(&mut dom, root, "form", &[("method", "get")]);
287 let image = el(
288 &mut dom,
289 form,
290 "input",
291 &[("type", "Image"), ("formmethod", "post")],
292 );
293 assert!(dom.is_submit_button(image));
294 assert_eq!(
295 dom.submit_detail(form, Some(image)).method,
296 FormMethod::Post
297 );
298 }
299
300 #[test]
301 fn submit_detail_reads_the_form_attributes() {
302 let mut dom: Dom = Dom::new();
303 let root = dom.root();
304 let form = el(
305 &mut dom,
306 root,
307 "form",
308 &[
309 ("action", "/save"),
310 ("method", "POST"),
311 ("enctype", "multipart/form-data"),
312 ("target", "_blank"),
313 ("novalidate", ""),
314 ],
315 );
316 let plain = el(&mut dom, form, "button", &[]);
317 let d = dom.submit_detail(form, Some(plain));
318 assert_eq!(d.submitter, Some(plain));
319 assert_eq!(d.action, "/save");
320 assert_eq!(d.method, FormMethod::Post);
321 assert_eq!(d.enctype, FormEnctype::MultipartFormData);
322 assert_eq!(d.target, "_blank");
323 assert!(d.no_validate);
324
325 let bare = el(&mut dom, root, "form", &[("method", "bogus")]);
326 let d = dom.submit_detail(bare, None);
327 assert_eq!(
328 (
329 d.action.as_str(),
330 d.method,
331 d.enctype,
332 d.target.as_str(),
333 d.no_validate
334 ),
335 ("", FormMethod::Get, FormEnctype::UrlEncoded, "", false)
336 );
337 }
338
339 #[test]
343 fn submit_detail_prefers_the_submit_buttons_overrides() {
344 let mut dom: Dom = Dom::new();
345 let root = dom.root();
346 let form = el(
347 &mut dom,
348 root,
349 "form",
350 &[("action", "/a"), ("method", "post"), ("target", "t")],
351 );
352 let over = el(
353 &mut dom,
354 form,
355 "input",
356 &[
357 ("type", "submit"),
358 ("formaction", "/b"),
359 ("formmethod", "dialog"),
360 ("formenctype", "text/plain"),
361 ("formtarget", "_self"),
362 ("formnovalidate", ""),
363 ],
364 );
365 let d = dom.submit_detail(form, Some(over));
366 assert_eq!(d.action, "/b");
367 assert_eq!(d.method, FormMethod::Dialog);
368 assert_eq!(d.enctype, FormEnctype::TextPlain);
369 assert_eq!(d.target, "_self");
370 assert!(d.no_validate);
371
372 let invalid = el(&mut dom, form, "button", &[("formmethod", "bogus")]);
373 assert_eq!(
374 dom.submit_detail(form, Some(invalid)).method,
375 FormMethod::Get
376 );
377
378 let reset = el(
380 &mut dom,
381 form,
382 "button",
383 &[("type", "reset"), ("formaction", "/r")],
384 );
385 let d = dom.submit_detail(form, Some(reset));
386 assert_eq!((d.submitter, d.action.as_str()), (Some(reset), "/a"));
387 }
388}