diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index cf68b60c90c..aa5693ad47d 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -121,11 +121,34 @@ const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen"; // But, this could be slower in performance and does have some discrepancies. For example, // they would try to vertically align text baseline with the baseline of other // TextNode within an inline flow. Another example is the horizontal scroll. -// FIXME(stevennovaryo): Implement lazily initiated placeholder. -// FIXME(stevennovaryo): Refactor these logic into a TextControl wrapper that would handle all textual input. +// FIXME(#38263): Refactor these logics into a TextControl wrapper that would decouple all textual input. struct InputTypeTextShadowTree { + inner_container: Dom, text_container: Dom, - placeholder_container: Dom, + placeholder_container: DomRefCell>>, +} + +impl InputTypeTextShadowTree { + /// Initialize the placeholder container only when it is necessary. This would help the performance of input + /// element with shadow dom that is quite bulky. + fn init_placeholder_container_if_necessary(&self, host: &HTMLInputElement, can_gc: CanGc) { + // If the container is already initialized or there is no placeholder then it is not necessary to + // initialize a new placeholder container. + if self.placeholder_container.borrow().is_some() || host.placeholder.borrow().is_empty() { + return; + } + + *self.placeholder_container.borrow_mut() = Some( + create_ua_widget_div_with_text_node( + &host.owner_document(), + self.inner_container.upcast::(), + PseudoElement::Placeholder, + true, + can_gc, + ) + .as_traced(), + ); + } } #[derive(Clone, JSTraceable, MallocSizeOf)] @@ -147,6 +170,40 @@ enum ShadowTree { // TODO: Add shadow trees for other input types (range etc) here } +/// Create a div element with a text node within an UA Widget and either append or prepend it to +/// the designated parent. This is used to create the text container for input elements. +fn create_ua_widget_div_with_text_node( + document: &Document, + parent: &Node, + implemented_pseudo: PseudoElement, + as_first_child: bool, + can_gc: CanGc, +) -> DomRoot { + let el = HTMLDivElement::new(local_name!("div"), None, document, None, can_gc); + parent + .upcast::() + .AppendChild(el.upcast::(), can_gc) + .unwrap(); + el.upcast::() + .set_implemented_pseudo_element(implemented_pseudo); + let text_node = document.CreateTextNode("".into(), can_gc); + + if !as_first_child { + el.upcast::() + .AppendChild(text_node.upcast::(), can_gc) + .unwrap(); + } else { + el.upcast::() + .InsertBefore( + text_node.upcast::(), + el.upcast::().GetFirstChild().as_deref(), + can_gc, + ) + .unwrap(); + } + el +} + /// #[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq)] #[allow(dead_code)] @@ -1106,30 +1163,6 @@ impl HTMLInputElement { .unwrap_or_else(|| self.upcast::().attach_ua_shadow_root(true, can_gc)) } - /// Create a div element with a text node within an UA Widget. - /// This will be used to create the text container for - /// input elements. - fn create_ua_widget_div_with_text_node( - &self, - document: &Document, - parent: &Node, - implemented_pseudo: PseudoElement, - can_gc: CanGc, - ) -> DomRoot { - let el = HTMLDivElement::new(local_name!("div"), None, document, None, can_gc); - parent - .upcast::() - .AppendChild(el.upcast::(), can_gc) - .unwrap(); - el.upcast::() - .set_implemented_pseudo_element(implemented_pseudo); - let text_node = document.CreateTextNode("".into(), can_gc); - el.upcast::() - .AppendChild(text_node.upcast::(), can_gc) - .unwrap(); - el - } - fn create_text_shadow_tree(&self, can_gc: CanGc) { let document = self.owner_document(); let shadow_root = self.shadow_root(can_gc); @@ -1145,17 +1178,11 @@ impl HTMLInputElement { .upcast::() .set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer); - let placeholder_container = self.create_ua_widget_div_with_text_node( - &document, - inner_container.upcast::(), - PseudoElement::Placeholder, - can_gc, - ); - - let text_container = self.create_ua_widget_div_with_text_node( + let text_container = create_ua_widget_div_with_text_node( &document, inner_container.upcast::(), PseudoElement::ServoTextControlInnerEditor, + false, can_gc, ); @@ -1163,8 +1190,9 @@ impl HTMLInputElement { .shadow_tree .borrow_mut() .insert(ShadowTree::Text(InputTypeTextShadowTree { + inner_container: inner_container.as_traced(), text_container: text_container.as_traced(), - placeholder_container: placeholder_container.as_traced(), + placeholder_container: DomRefCell::new(None), })); } @@ -1292,12 +1320,12 @@ impl HTMLInputElement { (true, _) => "\u{200B}".into(), }; - // FIXME(stevennovaryo): Refactor this inside a TextControl wrapper + // We are finding and updating the CharacterData child directly to optimize the update. text_shadow_tree .text_container .upcast::() .GetFirstChild() - .expect("Text container without child") + .expect("UA widget text container without child") .downcast::() .expect("First child is not a CharacterData node") .SetData(value_text); @@ -2263,14 +2291,21 @@ impl HTMLInputElement { return; } + let text_shadow_tree = self.text_shadow_tree(can_gc); + text_shadow_tree.init_placeholder_container_if_necessary(self, can_gc); + + let Some(ref placeholder_container) = *text_shadow_tree.placeholder_container.borrow() + else { + // No update is necesssary. + return; + }; let placeholder_text = self.placeholder.borrow().clone(); - // FIXME(stevennovaryo): Refactor this inside a TextControl wrapper - self.text_shadow_tree(can_gc) - .placeholder_container + // We are finding and updating the CharacterData child directly to optimize the update. + placeholder_container .upcast::() .GetFirstChild() - .expect("Text container without child") + .expect("UA widget text container without child") .downcast::() .expect("First child is not a CharacterData node") .SetData(placeholder_text);