we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement form element parsing and DOM interfaces (Phase 16)

Add HTML form-related elements to the parser and DOM tree:
- Tree builder: form-specific rules for <form>, <fieldset>, <button>,
<textarea>, <select>, <option>, <optgroup> with proper scope handling
- InSelect insertion mode for <select> content model
- Form element pointer per HTML spec §13.2.4.1
- DOM interfaces: form_owner(), form_elements(), label_control(),
get_element_by_id(), is_form_control()
- 18 new tests covering parsing and attribute access for all form elements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+691
+226
crates/dom/src/lib.rs
··· 427 427 self.nodes[node.0].next_sibling = None; 428 428 } 429 429 430 + // --- Form-specific DOM interfaces (Phase 16) --- 431 + 432 + /// Returns true if the element is a form-associated control 433 + /// (input, select, textarea, button, fieldset, or output). 434 + pub fn is_form_control(&self, node: NodeId) -> bool { 435 + matches!( 436 + self.tag_name(node), 437 + Some("input" | "select" | "textarea" | "button" | "fieldset" | "output") 438 + ) 439 + } 440 + 441 + /// Find the owning `<form>` for a form control by walking up ancestors. 442 + /// Returns `None` if the control is not inside a `<form>`. 443 + pub fn form_owner(&self, node: NodeId) -> Option<NodeId> { 444 + let mut current = self.parent(node); 445 + while let Some(id) = current { 446 + if self.tag_name(id) == Some("form") { 447 + return Some(id); 448 + } 449 + current = self.parent(id); 450 + } 451 + None 452 + } 453 + 454 + /// Collect all form-associated elements that are descendants of the given 455 + /// `<form>` element. Returns them in tree order. 456 + pub fn form_elements(&self, form: NodeId) -> Vec<NodeId> { 457 + let mut result = Vec::new(); 458 + self.collect_form_elements(form, &mut result); 459 + result 460 + } 461 + 462 + fn collect_form_elements(&self, node: NodeId, result: &mut Vec<NodeId>) { 463 + let mut child = self.nodes[node.0].first_child; 464 + while let Some(id) = child { 465 + if self.is_form_control(id) { 466 + result.push(id); 467 + } 468 + self.collect_form_elements(id, result); 469 + child = self.nodes[id.0].next_sibling; 470 + } 471 + } 472 + 473 + /// Resolve a `<label>` element's `for` attribute to its labeled control. 474 + /// If the `for` attribute is present, searches the entire document for an 475 + /// element with a matching `id`. If not present, returns the first 476 + /// form control descendant of the label. 477 + pub fn label_control(&self, label: NodeId) -> Option<NodeId> { 478 + if self.tag_name(label) != Some("label") { 479 + return None; 480 + } 481 + // Check `for` attribute. 482 + if let Some(for_id) = self.get_attribute(label, "for") { 483 + let for_id = for_id.to_string(); 484 + return self.get_element_by_id(&for_id); 485 + } 486 + // No `for` attribute: return first form control descendant. 487 + self.first_form_control_descendant(label) 488 + } 489 + 490 + /// Find an element by its `id` attribute (searches entire document tree). 491 + pub fn get_element_by_id(&self, id: &str) -> Option<NodeId> { 492 + self.find_element_by_id(self.root, id) 493 + } 494 + 495 + fn find_element_by_id(&self, node: NodeId, id: &str) -> Option<NodeId> { 496 + if self.get_attribute(node, "id") == Some(id) { 497 + return Some(node); 498 + } 499 + let mut child = self.nodes[node.0].first_child; 500 + while let Some(c) = child { 501 + if let Some(found) = self.find_element_by_id(c, id) { 502 + return Some(found); 503 + } 504 + child = self.nodes[c.0].next_sibling; 505 + } 506 + None 507 + } 508 + 509 + fn first_form_control_descendant(&self, node: NodeId) -> Option<NodeId> { 510 + let mut child = self.nodes[node.0].first_child; 511 + while let Some(id) = child { 512 + if self.is_form_control(id) { 513 + return Some(id); 514 + } 515 + if let Some(found) = self.first_form_control_descendant(id) { 516 + return Some(found); 517 + } 518 + child = self.nodes[id.0].next_sibling; 519 + } 520 + None 521 + } 522 + 430 523 /// Estimated heap bytes used by the DOM tree. 431 524 pub fn memory_usage(&self) -> usize { 432 525 let node_struct_size = std::mem::size_of::<Node>(); ··· 967 1060 } 968 1061 _ => panic!("expected elements"), 969 1062 } 1063 + } 1064 + 1065 + // --- Form-specific DOM interface tests --- 1066 + 1067 + #[test] 1068 + fn is_form_control() { 1069 + let mut doc = Document::new(); 1070 + let input = doc.create_element("input"); 1071 + let select = doc.create_element("select"); 1072 + let textarea = doc.create_element("textarea"); 1073 + let button = doc.create_element("button"); 1074 + let fieldset = doc.create_element("fieldset"); 1075 + let div = doc.create_element("div"); 1076 + let span = doc.create_element("span"); 1077 + 1078 + assert!(doc.is_form_control(input)); 1079 + assert!(doc.is_form_control(select)); 1080 + assert!(doc.is_form_control(textarea)); 1081 + assert!(doc.is_form_control(button)); 1082 + assert!(doc.is_form_control(fieldset)); 1083 + assert!(!doc.is_form_control(div)); 1084 + assert!(!doc.is_form_control(span)); 1085 + } 1086 + 1087 + #[test] 1088 + fn form_owner_finds_ancestor_form() { 1089 + let mut doc = Document::new(); 1090 + let root = doc.root(); 1091 + let form = doc.create_element("form"); 1092 + let div = doc.create_element("div"); 1093 + let input = doc.create_element("input"); 1094 + 1095 + doc.append_child(root, form); 1096 + doc.append_child(form, div); 1097 + doc.append_child(div, input); 1098 + 1099 + assert_eq!(doc.form_owner(input), Some(form)); 1100 + assert_eq!(doc.form_owner(div), Some(form)); 1101 + assert_eq!(doc.form_owner(form), None); 1102 + } 1103 + 1104 + #[test] 1105 + fn form_owner_returns_none_outside_form() { 1106 + let mut doc = Document::new(); 1107 + let root = doc.root(); 1108 + let div = doc.create_element("div"); 1109 + let input = doc.create_element("input"); 1110 + 1111 + doc.append_child(root, div); 1112 + doc.append_child(div, input); 1113 + 1114 + assert_eq!(doc.form_owner(input), None); 1115 + } 1116 + 1117 + #[test] 1118 + fn form_elements_collects_controls() { 1119 + let mut doc = Document::new(); 1120 + let root = doc.root(); 1121 + let form = doc.create_element("form"); 1122 + let input1 = doc.create_element("input"); 1123 + let input2 = doc.create_element("input"); 1124 + let select = doc.create_element("select"); 1125 + let div = doc.create_element("div"); 1126 + let textarea = doc.create_element("textarea"); 1127 + 1128 + doc.append_child(root, form); 1129 + doc.append_child(form, input1); 1130 + doc.append_child(form, div); 1131 + doc.append_child(div, input2); 1132 + doc.append_child(form, select); 1133 + doc.append_child(form, textarea); 1134 + 1135 + let elements = doc.form_elements(form); 1136 + assert_eq!(elements, vec![input1, input2, select, textarea]); 1137 + } 1138 + 1139 + #[test] 1140 + fn label_control_with_for_attribute() { 1141 + let mut doc = Document::new(); 1142 + let root = doc.root(); 1143 + let form = doc.create_element("form"); 1144 + let label = doc.create_element("label"); 1145 + let input = doc.create_element("input"); 1146 + 1147 + doc.set_attribute(label, "for", "username"); 1148 + doc.set_attribute(input, "id", "username"); 1149 + 1150 + doc.append_child(root, form); 1151 + doc.append_child(form, label); 1152 + doc.append_child(form, input); 1153 + 1154 + assert_eq!(doc.label_control(label), Some(input)); 1155 + } 1156 + 1157 + #[test] 1158 + fn label_control_implicit_association() { 1159 + let mut doc = Document::new(); 1160 + let root = doc.root(); 1161 + let label = doc.create_element("label"); 1162 + let text = doc.create_text("Name: "); 1163 + let input = doc.create_element("input"); 1164 + 1165 + doc.append_child(root, label); 1166 + doc.append_child(label, text); 1167 + doc.append_child(label, input); 1168 + 1169 + // No `for` attribute: should return first form control descendant. 1170 + assert_eq!(doc.label_control(label), Some(input)); 1171 + } 1172 + 1173 + #[test] 1174 + fn label_control_returns_none_for_non_label() { 1175 + let mut doc = Document::new(); 1176 + let div = doc.create_element("div"); 1177 + assert_eq!(doc.label_control(div), None); 1178 + } 1179 + 1180 + #[test] 1181 + fn get_element_by_id() { 1182 + let mut doc = Document::new(); 1183 + let root = doc.root(); 1184 + let div = doc.create_element("div"); 1185 + let input = doc.create_element("input"); 1186 + 1187 + doc.set_attribute(div, "id", "container"); 1188 + doc.set_attribute(input, "id", "email"); 1189 + 1190 + doc.append_child(root, div); 1191 + doc.append_child(div, input); 1192 + 1193 + assert_eq!(doc.get_element_by_id("container"), Some(div)); 1194 + assert_eq!(doc.get_element_by_id("email"), Some(input)); 1195 + assert_eq!(doc.get_element_by_id("nonexistent"), None); 970 1196 } 971 1197 }
+465
crates/html/src/tree_builder.rs
··· 17 17 Text, 18 18 AfterHead, 19 19 InBody, 20 + InSelect, 20 21 AfterBody, 21 22 AfterAfterBody, 22 23 } ··· 52 53 open_elements: Vec<NodeId>, 53 54 head_element: Option<NodeId>, 54 55 body_element: Option<NodeId>, 56 + /// The form element pointer (per HTML spec §13.2.4.1). 57 + form_element: Option<NodeId>, 55 58 insertion_mode: InsertionMode, 56 59 /// Original insertion mode, saved when switching to Text mode. 57 60 original_insertion_mode: Option<InsertionMode>, ··· 69 72 open_elements: Vec::new(), 70 73 head_element: None, 71 74 body_element: None, 75 + form_element: None, 72 76 insertion_mode: InsertionMode::Initial, 73 77 original_insertion_mode: None, 74 78 pending_text: String::new(), ··· 92 96 InsertionMode::Text => self.handle_text(token), 93 97 InsertionMode::AfterHead => self.handle_after_head(token), 94 98 InsertionMode::InBody => self.handle_in_body(token), 99 + InsertionMode::InSelect => self.handle_in_select(token), 95 100 InsertionMode::AfterBody => self.handle_after_body(token), 96 101 InsertionMode::AfterAfterBody => self.handle_after_after_body(token), 97 102 } ··· 385 390 self.insert_node(elem); 386 391 self.open_elements.push(elem); 387 392 } 393 + // --- Form elements (Phase 16) --- 394 + Token::StartTag { ref name, .. } if name == "form" => { 395 + // Per spec: if the form element pointer is not null, ignore. 396 + if self.form_element.is_some() { 397 + // Parse error, ignore the token. 398 + } else { 399 + if self.has_element_in_button_scope("p") { 400 + self.close_p_element(); 401 + } 402 + let elem = self.create_element_from_token(&token); 403 + self.insert_node(elem); 404 + self.open_elements.push(elem); 405 + self.form_element = Some(elem); 406 + } 407 + } 408 + Token::StartTag { ref name, .. } if name == "fieldset" => { 409 + if self.has_element_in_button_scope("p") { 410 + self.close_p_element(); 411 + } 412 + let elem = self.create_element_from_token(&token); 413 + self.insert_node(elem); 414 + self.open_elements.push(elem); 415 + } 416 + Token::StartTag { ref name, .. } if name == "button" => { 417 + // If there's a button in scope, close it first. 418 + if self.has_element_in_scope("button") { 419 + self.generate_implied_end_tags(None); 420 + self.pop_until("button"); 421 + } 422 + let elem = self.create_element_from_token(&token); 423 + self.insert_node(elem); 424 + self.open_elements.push(elem); 425 + } 426 + Token::StartTag { ref name, .. } if name == "textarea" => { 427 + let elem = self.create_element_from_token(&token); 428 + self.insert_node(elem); 429 + self.open_elements.push(elem); 430 + // Switch to Text mode to collect raw text content. 431 + self.original_insertion_mode = Some(self.insertion_mode); 432 + self.insertion_mode = InsertionMode::Text; 433 + } 434 + Token::StartTag { ref name, .. } if name == "select" => { 435 + let elem = self.create_element_from_token(&token); 436 + self.insert_node(elem); 437 + self.open_elements.push(elem); 438 + self.insertion_mode = InsertionMode::InSelect; 439 + } 440 + Token::StartTag { ref name, .. } if name == "optgroup" || name == "option" => { 441 + // Close any currently open <option>. 442 + if let Some(&top) = self.open_elements.last() { 443 + if self.document.tag_name(top) == Some("option") { 444 + self.open_elements.pop(); 445 + } 446 + } 447 + // Also close open <optgroup> when a new <optgroup> starts. 448 + if name == "optgroup" { 449 + if let Some(&top) = self.open_elements.last() { 450 + if self.document.tag_name(top) == Some("optgroup") { 451 + self.open_elements.pop(); 452 + } 453 + } 454 + } 455 + let elem = self.create_element_from_token(&token); 456 + self.insert_node(elem); 457 + self.open_elements.push(elem); 458 + } 459 + // --- SVG / iframe / void / generic start tags --- 388 460 Token::StartTag { ref name, .. } if name == "svg" => { 389 461 let elem = self.create_svg_element_from_token(&token); 390 462 self.insert_node(elem); ··· 431 503 } 432 504 self.close_p_element(); 433 505 } 506 + // --- Form end tags (Phase 16) --- 507 + Token::EndTag { ref name } if name == "form" => { 508 + // Per spec: reset the form element pointer, then pop. 509 + self.form_element = None; 510 + if self.has_element_in_scope("form") { 511 + self.generate_implied_end_tags(Some("form")); 512 + self.pop_until("form"); 513 + } 514 + } 515 + Token::EndTag { ref name } if name == "button" => { 516 + if self.has_element_in_scope("button") { 517 + self.generate_implied_end_tags(None); 518 + self.pop_until("button"); 519 + } 520 + } 521 + Token::EndTag { ref name } if name == "fieldset" => { 522 + if self.has_element_in_scope("fieldset") { 523 + self.generate_implied_end_tags(None); 524 + self.pop_until("fieldset"); 525 + } 526 + } 527 + Token::EndTag { ref name } if name == "optgroup" || name == "option" => { 528 + if self.has_element_in_scope(name) { 529 + self.generate_implied_end_tags(Some(name)); 530 + self.pop_until(name); 531 + } 532 + } 533 + // --- End of form end tags --- 434 534 Token::EndTag { ref name } 435 535 if name == "div" 436 536 || name == "pre" ··· 474 574 } 475 575 } 476 576 577 + /// Handle tokens inside a `<select>` element (InSelect insertion mode). 578 + fn handle_in_select(&mut self, token: Token) { 579 + match token { 580 + Token::Character(s) => { 581 + self.insert_text(&s); 582 + } 583 + Token::Comment(data) => { 584 + self.insert_comment(&data); 585 + } 586 + Token::StartTag { ref name, .. } if name == "option" => { 587 + // Close any currently open <option>. 588 + if let Some(&top) = self.open_elements.last() { 589 + if self.document.tag_name(top) == Some("option") { 590 + self.open_elements.pop(); 591 + } 592 + } 593 + let elem = self.create_element_from_token(&token); 594 + self.insert_node(elem); 595 + self.open_elements.push(elem); 596 + } 597 + Token::StartTag { ref name, .. } if name == "optgroup" => { 598 + // Close any open <option>, then close any open <optgroup>. 599 + if let Some(&top) = self.open_elements.last() { 600 + if self.document.tag_name(top) == Some("option") { 601 + self.open_elements.pop(); 602 + } 603 + } 604 + if let Some(&top) = self.open_elements.last() { 605 + if self.document.tag_name(top) == Some("optgroup") { 606 + self.open_elements.pop(); 607 + } 608 + } 609 + let elem = self.create_element_from_token(&token); 610 + self.insert_node(elem); 611 + self.open_elements.push(elem); 612 + } 613 + Token::EndTag { ref name } if name == "option" => { 614 + if let Some(&top) = self.open_elements.last() { 615 + if self.document.tag_name(top) == Some("option") { 616 + self.open_elements.pop(); 617 + } 618 + } 619 + } 620 + Token::EndTag { ref name } if name == "optgroup" => { 621 + // If the top is <option>, pop it first. 622 + if let Some(&top) = self.open_elements.last() { 623 + if self.document.tag_name(top) == Some("option") { 624 + self.open_elements.pop(); 625 + } 626 + } 627 + if let Some(&top) = self.open_elements.last() { 628 + if self.document.tag_name(top) == Some("optgroup") { 629 + self.open_elements.pop(); 630 + } 631 + } 632 + } 633 + Token::EndTag { ref name } if name == "select" => { 634 + self.pop_until("select"); 635 + self.insertion_mode = InsertionMode::InBody; 636 + } 637 + Token::StartTag { ref name, .. } if name == "select" => { 638 + // Nested <select>: close the current one (parse error). 639 + self.pop_until("select"); 640 + self.insertion_mode = InsertionMode::InBody; 641 + } 642 + Token::StartTag { ref name, .. } if name == "input" || name == "textarea" => { 643 + // Per spec: these close the <select> and reprocess in InBody. 644 + self.pop_until("select"); 645 + self.insertion_mode = InsertionMode::InBody; 646 + self.handle_in_body(token); 647 + } 648 + Token::Eof => { 649 + // Stop parsing. 650 + } 651 + _ => { 652 + // Ignore anything else in select. 653 + } 654 + } 655 + } 656 + 477 657 fn handle_after_body(&mut self, token: Token) { 478 658 match token { 479 659 Token::Character(ref s) if s.chars().all(|c| c.is_ascii_whitespace()) => { ··· 851 1031 | "iframe" 852 1032 | "img" 853 1033 | "input" 1034 + | "legend" 854 1035 | "li" 855 1036 | "link" 856 1037 | "listing" ··· 1328 1509 if let NodeData::Element { ref namespace, .. } = *doc.node_data(p) { 1329 1510 assert_eq!(namespace.as_deref(), None); 1330 1511 } 1512 + } 1513 + 1514 + // --- Form element parsing tests (Phase 16) --- 1515 + 1516 + #[test] 1517 + fn parse_form_with_inputs() { 1518 + let doc = parse_html( 1519 + r#"<form action="/submit" method="post"><input type="text" name="user"><input type="password" name="pass"><button type="submit">Login</button></form>"#, 1520 + ); 1521 + let root = doc.root(); 1522 + let html = doc.children(root).next().unwrap(); 1523 + let body = doc.children(html).nth(1).unwrap(); 1524 + let form = doc.children(body).next().unwrap(); 1525 + 1526 + assert_eq!(doc.tag_name(form), Some("form")); 1527 + assert_eq!(doc.get_attribute(form, "action"), Some("/submit")); 1528 + assert_eq!(doc.get_attribute(form, "method"), Some("post")); 1529 + 1530 + let tags = child_tags(&doc, form); 1531 + assert_eq!(tags, vec!["input", "input", "button"]); 1532 + 1533 + // Check input attributes. 1534 + let children: Vec<NodeId> = doc.children(form).collect(); 1535 + assert_eq!(doc.get_attribute(children[0], "type"), Some("text")); 1536 + assert_eq!(doc.get_attribute(children[0], "name"), Some("user")); 1537 + assert_eq!(doc.get_attribute(children[1], "type"), Some("password")); 1538 + assert_eq!(doc.get_attribute(children[1], "name"), Some("pass")); 1539 + 1540 + // Button contains text. 1541 + assert_eq!(doc.get_attribute(children[2], "type"), Some("submit")); 1542 + assert_eq!(text_of_children(&doc, children[2]), "Login"); 1543 + } 1544 + 1545 + #[test] 1546 + fn parse_textarea() { 1547 + let doc = parse_html( 1548 + r#"<form><textarea name="bio" rows="4" cols="50">Default text here</textarea></form>"#, 1549 + ); 1550 + let root = doc.root(); 1551 + let html = doc.children(root).next().unwrap(); 1552 + let body = doc.children(html).nth(1).unwrap(); 1553 + let form = doc.children(body).next().unwrap(); 1554 + let textarea = doc.children(form).next().unwrap(); 1555 + 1556 + assert_eq!(doc.tag_name(textarea), Some("textarea")); 1557 + assert_eq!(doc.get_attribute(textarea, "name"), Some("bio")); 1558 + assert_eq!(doc.get_attribute(textarea, "rows"), Some("4")); 1559 + assert_eq!(doc.get_attribute(textarea, "cols"), Some("50")); 1560 + assert_eq!(text_of_children(&doc, textarea), "Default text here"); 1561 + } 1562 + 1563 + #[test] 1564 + fn parse_select_with_options() { 1565 + let doc = parse_html( 1566 + r#"<form><select name="color"><option value="r">Red</option><option value="g" selected>Green</option><option value="b">Blue</option></select></form>"#, 1567 + ); 1568 + let root = doc.root(); 1569 + let html = doc.children(root).next().unwrap(); 1570 + let body = doc.children(html).nth(1).unwrap(); 1571 + let form = doc.children(body).next().unwrap(); 1572 + let select = doc.children(form).next().unwrap(); 1573 + 1574 + assert_eq!(doc.tag_name(select), Some("select")); 1575 + assert_eq!(doc.get_attribute(select, "name"), Some("color")); 1576 + 1577 + let options: Vec<NodeId> = doc.children(select).collect(); 1578 + assert_eq!(options.len(), 3); 1579 + assert_eq!(doc.get_attribute(options[0], "value"), Some("r")); 1580 + assert_eq!(text_of_children(&doc, options[0]), "Red"); 1581 + assert_eq!(doc.get_attribute(options[1], "value"), Some("g")); 1582 + assert_eq!(doc.get_attribute(options[1], "selected"), Some("")); 1583 + assert_eq!(doc.get_attribute(options[2], "value"), Some("b")); 1584 + } 1585 + 1586 + #[test] 1587 + fn parse_select_with_optgroups() { 1588 + let doc = parse_html( 1589 + r#"<select><optgroup label="Primary"><option>Red</option><option>Blue</option></optgroup><optgroup label="Secondary"><option>Orange</option></optgroup></select>"#, 1590 + ); 1591 + let root = doc.root(); 1592 + let html = doc.children(root).next().unwrap(); 1593 + let body = doc.children(html).nth(1).unwrap(); 1594 + let select = doc.children(body).next().unwrap(); 1595 + 1596 + assert_eq!(doc.tag_name(select), Some("select")); 1597 + let groups = child_tags(&doc, select); 1598 + assert_eq!(groups, vec!["optgroup", "optgroup"]); 1599 + 1600 + let group1 = doc.children(select).next().unwrap(); 1601 + assert_eq!(doc.get_attribute(group1, "label"), Some("Primary")); 1602 + let options: Vec<String> = child_tags(&doc, group1); 1603 + assert_eq!(options, vec!["option", "option"]); 1604 + } 1605 + 1606 + #[test] 1607 + fn parse_fieldset_and_legend() { 1608 + let doc = parse_html( 1609 + r#"<form><fieldset><legend>Personal Info</legend><input type="text" name="name"><input type="email" name="email"></fieldset></form>"#, 1610 + ); 1611 + let root = doc.root(); 1612 + let html = doc.children(root).next().unwrap(); 1613 + let body = doc.children(html).nth(1).unwrap(); 1614 + let form = doc.children(body).next().unwrap(); 1615 + let fieldset = doc.children(form).next().unwrap(); 1616 + 1617 + assert_eq!(doc.tag_name(fieldset), Some("fieldset")); 1618 + 1619 + let fieldset_tags = child_tags(&doc, fieldset); 1620 + assert_eq!(fieldset_tags, vec!["legend", "input", "input"]); 1621 + 1622 + let legend = doc.children(fieldset).next().unwrap(); 1623 + assert_eq!(text_of_children(&doc, legend), "Personal Info"); 1624 + } 1625 + 1626 + #[test] 1627 + fn parse_label_with_for_attribute() { 1628 + let doc = parse_html( 1629 + r#"<form><label for="name">Name:</label><input type="text" id="name" name="name"></form>"#, 1630 + ); 1631 + let root = doc.root(); 1632 + let html = doc.children(root).next().unwrap(); 1633 + let body = doc.children(html).nth(1).unwrap(); 1634 + let form = doc.children(body).next().unwrap(); 1635 + 1636 + let tags = child_tags(&doc, form); 1637 + assert_eq!(tags, vec!["label", "input"]); 1638 + 1639 + let label = doc.children(form).next().unwrap(); 1640 + assert_eq!(doc.get_attribute(label, "for"), Some("name")); 1641 + assert_eq!(text_of_children(&doc, label), "Name:"); 1642 + 1643 + // Verify label_control resolves via `for` attribute. 1644 + let input = doc.children(form).nth(1).unwrap(); 1645 + assert_eq!(doc.label_control(label), Some(input)); 1646 + } 1647 + 1648 + #[test] 1649 + fn parse_label_implicit_association() { 1650 + let doc = parse_html(r#"<label>Name: <input type="text" name="name"></label>"#); 1651 + let root = doc.root(); 1652 + let html = doc.children(root).next().unwrap(); 1653 + let body = doc.children(html).nth(1).unwrap(); 1654 + let label = doc.children(body).next().unwrap(); 1655 + 1656 + assert_eq!(doc.tag_name(label), Some("label")); 1657 + let input = doc.label_control(label).unwrap(); 1658 + assert_eq!(doc.tag_name(input), Some("input")); 1659 + assert_eq!(doc.get_attribute(input, "name"), Some("name")); 1660 + } 1661 + 1662 + #[test] 1663 + fn form_closes_p_in_button_scope() { 1664 + let doc = parse_html("<p>text<form><input></form>"); 1665 + let root = doc.root(); 1666 + let html = doc.children(root).next().unwrap(); 1667 + let body = doc.children(html).nth(1).unwrap(); 1668 + 1669 + // <p> should be closed by <form>, so they're siblings. 1670 + let tags = child_tags(&doc, body); 1671 + assert_eq!(tags, vec!["p", "form"]); 1672 + } 1673 + 1674 + #[test] 1675 + fn nested_form_is_ignored() { 1676 + let doc = parse_html("<form id=\"outer\"><form id=\"inner\"><input></form></form>"); 1677 + let root = doc.root(); 1678 + let html = doc.children(root).next().unwrap(); 1679 + let body = doc.children(html).nth(1).unwrap(); 1680 + 1681 + // Only one <form> should exist (nested form is ignored). 1682 + let tags = child_tags(&doc, body); 1683 + assert_eq!(tags, vec!["form"]); 1684 + 1685 + let form = doc.children(body).next().unwrap(); 1686 + assert_eq!(doc.get_attribute(form, "id"), Some("outer")); 1687 + 1688 + // Input should be a child of the outer form. 1689 + let form_tags = child_tags(&doc, form); 1690 + assert_eq!(form_tags, vec!["input"]); 1691 + } 1692 + 1693 + #[test] 1694 + fn button_scope_handling() { 1695 + let doc = parse_html("<button>First</button><button>Second</button>"); 1696 + let root = doc.root(); 1697 + let html = doc.children(root).next().unwrap(); 1698 + let body = doc.children(html).nth(1).unwrap(); 1699 + 1700 + let tags = child_tags(&doc, body); 1701 + assert_eq!(tags, vec!["button", "button"]); 1702 + } 1703 + 1704 + #[test] 1705 + fn form_elements_collection() { 1706 + let doc = parse_html( 1707 + r#"<form><input name="a"><div><input name="b"></div><select name="c"><option>X</option></select><textarea name="d"></textarea></form>"#, 1708 + ); 1709 + let root = doc.root(); 1710 + let html = doc.children(root).next().unwrap(); 1711 + let body = doc.children(html).nth(1).unwrap(); 1712 + let form = doc.children(body).next().unwrap(); 1713 + 1714 + let elements = doc.form_elements(form); 1715 + assert_eq!(elements.len(), 4); 1716 + 1717 + // Verify they're the right elements. 1718 + assert_eq!(doc.get_attribute(elements[0], "name"), Some("a")); 1719 + assert_eq!(doc.get_attribute(elements[1], "name"), Some("b")); 1720 + assert_eq!(doc.get_attribute(elements[2], "name"), Some("c")); 1721 + assert_eq!(doc.get_attribute(elements[3], "name"), Some("d")); 1722 + } 1723 + 1724 + #[test] 1725 + fn form_owner_from_parsed_tree() { 1726 + let doc = parse_html(r#"<form id="f"><div><input id="i"></div></form>"#); 1727 + let root = doc.root(); 1728 + let html = doc.children(root).next().unwrap(); 1729 + let body = doc.children(html).nth(1).unwrap(); 1730 + let form = doc.children(body).next().unwrap(); 1731 + 1732 + let input = doc.get_element_by_id("i").unwrap(); 1733 + assert_eq!(doc.form_owner(input), Some(form)); 1734 + } 1735 + 1736 + #[test] 1737 + fn parse_input_types() { 1738 + let doc = parse_html( 1739 + r#"<form> 1740 + <input type="text"> 1741 + <input type="password"> 1742 + <input type="checkbox" checked> 1743 + <input type="radio" name="choice" value="a"> 1744 + <input type="submit" value="Go"> 1745 + <input type="reset"> 1746 + <input type="hidden" name="token" value="abc"> 1747 + <input type="number" min="0" max="100"> 1748 + <input type="email"> 1749 + <input type="url"> 1750 + <input type="search"> 1751 + <input type="tel"> 1752 + </form>"#, 1753 + ); 1754 + let root = doc.root(); 1755 + let html = doc.children(root).next().unwrap(); 1756 + let body = doc.children(html).nth(1).unwrap(); 1757 + let form = doc.children(body).next().unwrap(); 1758 + 1759 + let inputs: Vec<NodeId> = doc 1760 + .children(form) 1761 + .filter(|&id| doc.tag_name(id) == Some("input")) 1762 + .collect(); 1763 + assert_eq!(inputs.len(), 12); 1764 + 1765 + assert_eq!(doc.get_attribute(inputs[0], "type"), Some("text")); 1766 + assert_eq!(doc.get_attribute(inputs[1], "type"), Some("password")); 1767 + assert_eq!(doc.get_attribute(inputs[2], "type"), Some("checkbox")); 1768 + assert_eq!(doc.get_attribute(inputs[2], "checked"), Some("")); 1769 + assert_eq!(doc.get_attribute(inputs[3], "type"), Some("radio")); 1770 + assert_eq!(doc.get_attribute(inputs[3], "name"), Some("choice")); 1771 + assert_eq!(doc.get_attribute(inputs[4], "type"), Some("submit")); 1772 + assert_eq!(doc.get_attribute(inputs[4], "value"), Some("Go")); 1773 + assert_eq!(doc.get_attribute(inputs[5], "type"), Some("reset")); 1774 + assert_eq!(doc.get_attribute(inputs[6], "type"), Some("hidden")); 1775 + assert_eq!(doc.get_attribute(inputs[6], "value"), Some("abc")); 1776 + assert_eq!(doc.get_attribute(inputs[7], "type"), Some("number")); 1777 + assert_eq!(doc.get_attribute(inputs[7], "min"), Some("0")); 1778 + assert_eq!(doc.get_attribute(inputs[7], "max"), Some("100")); 1779 + assert_eq!(doc.get_attribute(inputs[8], "type"), Some("email")); 1780 + assert_eq!(doc.get_attribute(inputs[9], "type"), Some("url")); 1781 + assert_eq!(doc.get_attribute(inputs[10], "type"), Some("search")); 1782 + assert_eq!(doc.get_attribute(inputs[11], "type"), Some("tel")); 1783 + } 1784 + 1785 + #[test] 1786 + fn select_closes_on_input() { 1787 + // An <input> inside a <select> should close the select. 1788 + let doc = parse_html("<select><option>A</option><input type=\"text\"></select>"); 1789 + let root = doc.root(); 1790 + let html = doc.children(root).next().unwrap(); 1791 + let body = doc.children(html).nth(1).unwrap(); 1792 + 1793 + let tags = child_tags(&doc, body); 1794 + // Select should be closed before input, making them siblings. 1795 + assert_eq!(tags, vec!["select", "input"]); 1331 1796 } 1332 1797 }