mirror of
https://github.com/servo/servo.git
synced 2025-08-26 07:38:21 +01:00
dom: Implement textual <input>
lazy initialized placeholder (#38821)
Following #37527, every textual input is constructing the containers for value and placeholder. However not all of textual `<input>` element require the initialization of such placeholder container. This is apparent with JS UI framework that defines its own placeholder management. This PR add lazy initialization for placeholder which construct the relevant HTML elements for placeholder container whenever it is necessary. Testing: Existing WPT coverage Signed-off-by: Jo Steven Novaryo <jo.steven.novaryo@huawei.com>
This commit is contained in:
parent
66b0eb2687
commit
0026213799
1 changed files with 77 additions and 42 deletions
|
@ -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,
|
// But, this could be slower in performance and does have some discrepancies. For example,
|
||||||
// they would try to vertically align <input> text baseline with the baseline of other
|
// they would try to vertically align <input> text baseline with the baseline of other
|
||||||
// TextNode within an inline flow. Another example is the horizontal scroll.
|
// TextNode within an inline flow. Another example is the horizontal scroll.
|
||||||
// FIXME(stevennovaryo): Implement lazily initiated placeholder.
|
// FIXME(#38263): Refactor these logics into a TextControl wrapper that would decouple all textual input.
|
||||||
// FIXME(stevennovaryo): Refactor these logic into a TextControl wrapper that would handle all textual input.
|
|
||||||
struct InputTypeTextShadowTree {
|
struct InputTypeTextShadowTree {
|
||||||
|
inner_container: Dom<HTMLDivElement>,
|
||||||
text_container: Dom<HTMLDivElement>,
|
text_container: Dom<HTMLDivElement>,
|
||||||
placeholder_container: Dom<HTMLDivElement>,
|
placeholder_container: DomRefCell<Option<Dom<HTMLDivElement>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Node>(),
|
||||||
|
PseudoElement::Placeholder,
|
||||||
|
true,
|
||||||
|
can_gc,
|
||||||
|
)
|
||||||
|
.as_traced(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||||
|
@ -147,6 +170,40 @@ enum ShadowTree {
|
||||||
// TODO: Add shadow trees for other input types (range etc) here
|
// 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<HTMLDivElement> {
|
||||||
|
let el = HTMLDivElement::new(local_name!("div"), None, document, None, can_gc);
|
||||||
|
parent
|
||||||
|
.upcast::<Node>()
|
||||||
|
.AppendChild(el.upcast::<Node>(), can_gc)
|
||||||
|
.unwrap();
|
||||||
|
el.upcast::<Node>()
|
||||||
|
.set_implemented_pseudo_element(implemented_pseudo);
|
||||||
|
let text_node = document.CreateTextNode("".into(), can_gc);
|
||||||
|
|
||||||
|
if !as_first_child {
|
||||||
|
el.upcast::<Node>()
|
||||||
|
.AppendChild(text_node.upcast::<Node>(), can_gc)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
el.upcast::<Node>()
|
||||||
|
.InsertBefore(
|
||||||
|
text_node.upcast::<Node>(),
|
||||||
|
el.upcast::<Node>().GetFirstChild().as_deref(),
|
||||||
|
can_gc,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
el
|
||||||
|
}
|
||||||
|
|
||||||
/// <https://html.spec.whatwg.org/multipage/#attr-input-type>
|
/// <https://html.spec.whatwg.org/multipage/#attr-input-type>
|
||||||
#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -1106,30 +1163,6 @@ impl HTMLInputElement {
|
||||||
.unwrap_or_else(|| self.upcast::<Element>().attach_ua_shadow_root(true, can_gc))
|
.unwrap_or_else(|| self.upcast::<Element>().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<HTMLDivElement> {
|
|
||||||
let el = HTMLDivElement::new(local_name!("div"), None, document, None, can_gc);
|
|
||||||
parent
|
|
||||||
.upcast::<Node>()
|
|
||||||
.AppendChild(el.upcast::<Node>(), can_gc)
|
|
||||||
.unwrap();
|
|
||||||
el.upcast::<Node>()
|
|
||||||
.set_implemented_pseudo_element(implemented_pseudo);
|
|
||||||
let text_node = document.CreateTextNode("".into(), can_gc);
|
|
||||||
el.upcast::<Node>()
|
|
||||||
.AppendChild(text_node.upcast::<Node>(), can_gc)
|
|
||||||
.unwrap();
|
|
||||||
el
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_text_shadow_tree(&self, can_gc: CanGc) {
|
fn create_text_shadow_tree(&self, can_gc: CanGc) {
|
||||||
let document = self.owner_document();
|
let document = self.owner_document();
|
||||||
let shadow_root = self.shadow_root(can_gc);
|
let shadow_root = self.shadow_root(can_gc);
|
||||||
|
@ -1145,17 +1178,11 @@ impl HTMLInputElement {
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
|
.set_implemented_pseudo_element(PseudoElement::ServoTextControlInnerContainer);
|
||||||
|
|
||||||
let placeholder_container = self.create_ua_widget_div_with_text_node(
|
let text_container = create_ua_widget_div_with_text_node(
|
||||||
&document,
|
|
||||||
inner_container.upcast::<Node>(),
|
|
||||||
PseudoElement::Placeholder,
|
|
||||||
can_gc,
|
|
||||||
);
|
|
||||||
|
|
||||||
let text_container = self.create_ua_widget_div_with_text_node(
|
|
||||||
&document,
|
&document,
|
||||||
inner_container.upcast::<Node>(),
|
inner_container.upcast::<Node>(),
|
||||||
PseudoElement::ServoTextControlInnerEditor,
|
PseudoElement::ServoTextControlInnerEditor,
|
||||||
|
false,
|
||||||
can_gc,
|
can_gc,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1163,8 +1190,9 @@ impl HTMLInputElement {
|
||||||
.shadow_tree
|
.shadow_tree
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(ShadowTree::Text(InputTypeTextShadowTree {
|
.insert(ShadowTree::Text(InputTypeTextShadowTree {
|
||||||
|
inner_container: inner_container.as_traced(),
|
||||||
text_container: text_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(),
|
(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_shadow_tree
|
||||||
.text_container
|
.text_container
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.GetFirstChild()
|
.GetFirstChild()
|
||||||
.expect("Text container without child")
|
.expect("UA widget text container without child")
|
||||||
.downcast::<CharacterData>()
|
.downcast::<CharacterData>()
|
||||||
.expect("First child is not a CharacterData node")
|
.expect("First child is not a CharacterData node")
|
||||||
.SetData(value_text);
|
.SetData(value_text);
|
||||||
|
@ -2263,14 +2291,21 @@ impl HTMLInputElement {
|
||||||
return;
|
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();
|
let placeholder_text = self.placeholder.borrow().clone();
|
||||||
|
|
||||||
// FIXME(stevennovaryo): Refactor this inside a TextControl wrapper
|
// We are finding and updating the CharacterData child directly to optimize the update.
|
||||||
self.text_shadow_tree(can_gc)
|
placeholder_container
|
||||||
.placeholder_container
|
|
||||||
.upcast::<Node>()
|
.upcast::<Node>()
|
||||||
.GetFirstChild()
|
.GetFirstChild()
|
||||||
.expect("Text container without child")
|
.expect("UA widget text container without child")
|
||||||
.downcast::<CharacterData>()
|
.downcast::<CharacterData>()
|
||||||
.expect("First child is not a CharacterData node")
|
.expect("First child is not a CharacterData node")
|
||||||
.SetData(placeholder_text);
|
.SetData(placeholder_text);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue