From 86bb5b7f736fda27b9c81c3a3a3a4a896105e9d7 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 3 Sep 2026 08:07:41 +0800 Subject: [PATCH] fix(forms): avoid quadratic selectedcontent synchronization --- moli-dom/src/native/host/collections/form.rs | 15 ++++++- moli-dom/src/native/mod.rs | 42 ++++++++++++++++++- .../mutation_commands/selectedcontent.rs | 12 +++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/moli-dom/src/native/host/collections/form.rs b/moli-dom/src/native/host/collections/form.rs index d34240d2cd..766091fde6 100644 --- a/moli-dom/src/native/host/collections/form.rs +++ b/moli-dom/src/native/host/collections/form.rs @@ -189,7 +189,20 @@ impl DomHost { } pub fn select_selectedcontent_elements(&self, handle: DomHandle) -> Vec { - self.dom.select_selectedcontent_elements(handle) + if !self.is_html_element_named(handle, "select") { + return Vec::new(); + } + self.elements_by_tag_name_ns( + handle, + Some("http://www.w3.org/1999/xhtml"), + "selectedcontent", + false, + ) + .into_iter() + .filter(|selectedcontent| { + self.selectedcontent_nearest_ancestor_select(*selectedcontent) == Some(handle) + }) + .collect() } pub fn option_is_disabled(&self, handle: DomHandle) -> bool { diff --git a/moli-dom/src/native/mod.rs b/moli-dom/src/native/mod.rs index d839de8a3e..cdbac926cd 100644 --- a/moli-dom/src/native/mod.rs +++ b/moli-dom/src/native/mod.rs @@ -1742,9 +1742,11 @@ mod tests { let body = host.document_body_handle().unwrap(); let select = host.create_element("select"); + assert!(host.append_child(body, select)); + assert!(host.select_selectedcontent_elements(select).is_empty()); + let button = host.create_element("button"); let selectedcontent = host.create_element("selectedcontent"); - assert!(host.append_child(body, select)); assert!(host.append_child(select, button)); assert!(host.append_child(button, selectedcontent)); assert_eq!( @@ -1776,6 +1778,44 @@ mod tests { host.select_selectedcontent_elements(select), vec![selectedcontent] ); + + assert!(host.remove_child(button, selectedcontent)); + assert!(host.select_selectedcontent_elements(select).is_empty()); + assert!(host.append_child(button, selectedcontent)); + assert_eq!( + host.select_selectedcontent_elements(select), + vec![selectedcontent] + ); + } + + #[test] + fn selectedcontent_query_index_preserves_detached_subtree_order_and_namespace() { + let mut host = DomHost::from_dom(NativeDom::new_html(test_url())); + let select = host.create_element("select"); + assert!(host.select_selectedcontent_elements(select).is_empty()); + + let first_button = host.create_element("button"); + let first = host.create_element("selectedcontent"); + let second_button = host.create_element("button"); + let second = host.create_element("selectedcontent"); + let foreign = host + .create_element_ns(Some("urn:selectedcontent-test"), "selectedcontent") + .expect("foreign selectedcontent element"); + assert!(host.append_child(select, first_button)); + assert!(host.append_child(first_button, first)); + assert!(host.append_child(select, second_button)); + assert!(host.append_child(second_button, second)); + assert!(host.append_child(select, foreign)); + + assert_eq!( + host.select_selectedcontent_elements(select), + vec![first, second] + ); + assert!(host.insert_before(select, second_button, Some(first_button))); + assert_eq!( + host.select_selectedcontent_elements(select), + vec![second, first] + ); } #[test] diff --git a/moli-renderer-v8/src/document_runtime/mutation_commands/selectedcontent.rs b/moli-renderer-v8/src/document_runtime/mutation_commands/selectedcontent.rs index 7df83555eb..37ff362b2b 100644 --- a/moli-renderer-v8/src/document_runtime/mutation_commands/selectedcontent.rs +++ b/moli-renderer-v8/src/document_runtime/mutation_commands/selectedcontent.rs @@ -260,6 +260,13 @@ impl DocumentRuntime { let Some(select) = self.dom_host.option_nearest_ancestor_select(option) else { return false; }; + if self + .dom_host + .select_selectedcontent_elements(select) + .is_empty() + { + return false; + } if self .dom_host .select_selected_option_elements(select) @@ -406,12 +413,15 @@ impl DocumentRuntime { return false; } + let targets = self.dom_host.select_selectedcontent_elements(select); + if targets.is_empty() { + return false; + } let selected_option = self .dom_host .select_selected_option_elements(select) .first() .copied(); - let targets = self.dom_host.select_selectedcontent_elements(select); let mut changed = false; for target in targets { changed |= self.clone_selected_option_contents_into_selectedcontent(