diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 9605947b7b..3cd03dff68 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -3282,7 +3282,6 @@ html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLU html/webappapis/dynamic-markup-insertion/html-unsafe-methods/Document-parseHTMLUnsafe.html html/webappapis/dynamic-markup-insertion/html-unsafe-methods/setHTMLUnsafe-xml.html html/webappapis/scripting/event-loops/microtask_before_prepare_the_script_element-01.html -html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html html/webappapis/scripting/events/event-handler-attributes-frameset-alt.html html/webappapis/scripting/events/event-handler-attributes-frameset-window.html html/webappapis/scripting/events/event-handler-attributes-window-frameset.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index b787ff5599..c1a97f0c65 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -6658,6 +6658,7 @@ html/webappapis/scripting/event-loops/microtask_after_script.html html/webappapis/scripting/event-loops/task_microtask_ordering.html html/webappapis/scripting/event-loops/update-the-rendering-resize-autofocus.html html/webappapis/scripting/events/body-onload.html +html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html html/webappapis/scripting/events/event-handler-all-global-events.html diff --git a/moli-dom/src/native/host.rs b/moli-dom/src/native/host.rs index b83f3adaed..60dc898e65 100644 --- a/moli-dom/src/native/host.rs +++ b/moli-dom/src/native/host.rs @@ -49,3 +49,18 @@ fn is_html_frame_owner_candidate(local_name: &str, namespace: &str) -> bool { .into_iter() .any(|name| local_name.eq_ignore_ascii_case(name)) } + +fn is_builtin_reassociateable_form_associated_element(element: &Element) -> bool { + element.namespace() == "http://www.w3.org/1999/xhtml" + && matches!( + element.local_name(), + "button" | "fieldset" | "input" | "object" | "output" | "select" | "textarea" + ) +} + +fn is_parser_form_association_candidate(element: &Element) -> bool { + // HTMLImageElement keeps a parser/ancestor form owner for legacy + // behavior, but is neither listed nor reassociateable. + is_builtin_reassociateable_form_associated_element(element) + || (element.namespace() == "http://www.w3.org/1999/xhtml" && element.local_name() == "img") +} diff --git a/moli-dom/src/native/host/collections/form.rs b/moli-dom/src/native/host/collections/form.rs index bab5c5b399..d34240d2cd 100644 --- a/moli-dom/src/native/host/collections/form.rs +++ b/moli-dom/src/native/host/collections/form.rs @@ -121,13 +121,20 @@ impl DomHost { .is_some_and(is_listed_form_control_element) } + pub fn builtin_form_associated_owner(&self, handle: DomHandle) -> Option { + let element = self.node(handle).and_then(Node::as_element)?; + if !is_parser_form_association_candidate(element) { + return None; + } + if is_builtin_reassociateable_form_associated_element(element) { + return self.form_control_owner(handle); + } + self.parser_or_ancestor_form_owner(handle) + } + pub fn form_control_owner(&self, handle: DomHandle) -> Option { let element = self.node(handle).and_then(Node::as_element)?; - if !matches!( - element.local_name(), - "button" | "fieldset" | "input" | "object" | "output" | "select" | "textarea" - ) || element.namespace() != "http://www.w3.org/1999/xhtml" - { + if !is_builtin_reassociateable_form_associated_element(element) { return None; } @@ -145,7 +152,14 @@ impl DomHost { .then_some(candidate); } - if let Some(owner) = element.parser_associated_form_owner() + self.parser_or_ancestor_form_owner(handle) + } + + fn parser_or_ancestor_form_owner(&self, handle: DomHandle) -> Option { + if let Some(owner) = self + .node(handle) + .and_then(Node::as_element) + .and_then(Element::parser_associated_form_owner) && self.is_html_element_named(owner, "form") && self.root_node_handle(handle) == self.root_node_handle(owner) { diff --git a/moli-dom/src/native/host/mutation/state.rs b/moli-dom/src/native/host/mutation/state.rs index 81ad15da14..14a625f217 100644 --- a/moli-dom/src/native/host/mutation/state.rs +++ b/moli-dom/src/native/host/mutation/state.rs @@ -92,6 +92,13 @@ impl DomHost { &mut self, handle: DomHandle, ) { + if !self + .node(handle) + .and_then(Node::as_element) + .is_some_and(is_builtin_reassociateable_form_associated_element) + { + return; + } self.clear_parser_associated_form_owner(handle); } diff --git a/moli-dom/src/native/host/parser.rs b/moli-dom/src/native/host/parser.rs index 4b37773c54..a7c38c63c2 100644 --- a/moli-dom/src/native/host/parser.rs +++ b/moli-dom/src/native/host/parser.rs @@ -114,12 +114,7 @@ impl DomHost { else { return false; }; - if element.namespace() != "http://www.w3.org/1999/xhtml" - || !matches!( - element.local_name(), - "button" | "fieldset" | "input" | "object" | "output" | "select" | "textarea" - ) - { + if !is_parser_form_association_candidate(element) { return false; } element.set_parser_associated_form_owner(Some(form)) diff --git a/moli-dom/src/native/mod.rs b/moli-dom/src/native/mod.rs index 4ddd9510de..18a5316cea 100644 --- a/moli-dom/src/native/mod.rs +++ b/moli-dom/src/native/mod.rs @@ -1427,6 +1427,45 @@ mod tests { assert_eq!(host.form_control_owner(nested_input), Some(form)); } + #[test] + fn image_form_owner_tracks_parser_and_ancestor_without_becoming_listed() { + let mut host = DomHost::from_dom(NativeDom::new_html(test_url())); + host.reset_html_document_shell(); + let body = host.document_body_handle().expect("document body"); + let parser_form = host.create_element("form"); + let ancestor_form = host.create_element("form"); + let image = host.create_parser_element_without_attributes( + "img".to_owned(), + "http://www.w3.org/1999/xhtml".to_owned(), + None, + ); + + assert!(host.set_attribute(ancestor_form, "id", "attribute-owner")); + assert!(host.append_child(body, parser_form)); + assert!(host.append_child(body, ancestor_form)); + assert!(host.associate_parser_form_owner(image, parser_form)); + assert!(host.append_child(body, image)); + assert_eq!(host.builtin_form_associated_owner(image), Some(parser_form)); + assert_eq!(host.form_control_owner(image), None); + assert!(host.form_control_elements(parser_form).is_empty()); + + assert!(host.set_attribute(image, "form", "attribute-owner")); + assert_eq!(host.builtin_form_associated_owner(image), Some(parser_form)); + + assert!(host.append_child(ancestor_form, image)); + assert_eq!( + host.builtin_form_associated_owner(image), + Some(ancestor_form) + ); + assert_eq!( + host.node(image) + .and_then(Node::as_element) + .and_then(Element::parser_associated_form_owner), + None + ); + assert!(host.form_control_elements(ancestor_form).is_empty()); + } + #[test] fn secondary_document_form_attribute_remains_authoritative() { let mut host = DomHost::from_dom(NativeDom::new_html(test_url())); diff --git a/moli-renderer-v8/src/native_bridge/element/forms/owner.rs b/moli-renderer-v8/src/native_bridge/element/forms/owner.rs index f1c2eea0f5..54d495929b 100644 --- a/moli-renderer-v8/src/native_bridge/element/forms/owner.rs +++ b/moli-renderer-v8/src/native_bridge/element/forms/owner.rs @@ -10,12 +10,9 @@ pub(crate) fn form_associated_form_owner( if is_form_associated_custom_element_handle(runtime, handle) { return custom_element_form_owner(runtime, handle, element); } - if !is_builtin_form_associated_element(element) { - return None; - } runtime .dom_host() - .form_control_owner(handle) + .builtin_form_associated_owner(handle) .filter(|candidate| { runtime .dom_host() @@ -59,7 +56,7 @@ fn custom_element_form_owner( None } -fn is_builtin_form_associated_element(element: &crate::dom::native::Element) -> bool { +fn has_reflected_builtin_form_owner(element: &crate::dom::native::Element) -> bool { element.namespace() == "http://www.w3.org/1999/xhtml" && matches!( element.local_name(), @@ -72,7 +69,7 @@ fn form_associated_reflected_form_owner( handle: DomHandle, ) -> Option { let element = runtime.dom_host().node(handle).and_then(Node::as_element)?; - if !is_builtin_form_associated_element(element) { + if !has_reflected_builtin_form_owner(element) { return None; } if let Some(form_id) = element.attribute("form") diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs index a70107e662..e71c67ec52 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs @@ -18,6 +18,7 @@ fn event_attribute_handlers_use_html_scope_chain_and_report_compile_errors() {
+ form image
`; @@ -63,6 +64,14 @@ fn event_attribute_handlers_use_html_scope_chain_and_report_compile_errors() { `); buttonInner.click(); + const formImage = document.getElementById("form-image"); + globalThis.elements = "global-elements"; + formImage.setAttribute( + "onclick", + `globalThis.__imageFormScope = elements;` + ); + formImage.click(); + globalThis.__windowScope = null; globalThis.__compileErrorEvents = 0; document.body.bodyOwn = true; @@ -84,6 +93,8 @@ fn event_attribute_handlers_use_html_scope_chain_and_report_compile_errors() { return JSON.stringify({ cell: globalThis.__cellScope, form: globalThis.__formScope, + imageForm: globalThis.__imageFormScope === form.elements, + imageIsNotListed: !Array.from(form.elements).includes(formImage), window: globalThis.__windowScope, errors: globalThis.__compileErrorEvents, }); @@ -94,7 +105,7 @@ fn event_attribute_handlers_use_html_scope_chain_and_report_compile_errors() { assert_eq!( result, - r#"{"cell":["number","string","function","boolean","undefined","object","updated"],"form":["boolean","object","string","string","boolean","boolean","undefined","object"],"window":["undefined","function","undefined","string"],"errors":1}"#, + r#"{"cell":["number","string","function","boolean","undefined","object","updated"],"form":["boolean","object","string","string","boolean","boolean","undefined","object"],"imageForm":true,"imageIsNotListed":true,"window":["undefined","function","undefined","string"],"errors":1}"#, ); }