mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Implement Input type=text
UA Shadow DOM (#37065)
Implement Shadow Tree construction for input `type=text`, adding a text control inner editor container and placeholder container. Subsequently, due to the changes of the DOM tree structure, the changes will add a new NodeFlag `IS_TEXT_CONTROL_INNER_EDITOR` to handle the following cases. - If a mouse click button event hits a text control inner editor, it will redirect the focus target to its shadow host. - In text run's construction, the text control inner editor container queries the selection from its shadow host. This is later used to resolve caret and selection painting in the display list. This will be the first step of fixing input `type=text` and other single-line text input element widgets. Such as, implementing `::placeholder` selector. Testing: Existing WPT test and new Servo specific appearance WPT. Fixes: #36307 --------- Signed-off-by: stevennovaryo <steven.novaryo@gmail.com>
This commit is contained in:
parent
578c52fe2b
commit
5580704438
24 changed files with 635 additions and 36 deletions
|
@ -230,6 +230,10 @@ bitflags! {
|
|||
/// Whether this node has a weird parser insertion mode. i.e whether setting innerHTML
|
||||
/// needs extra work or not
|
||||
const HAS_WEIRD_PARSER_INSERTION_MODE = 1 << 11;
|
||||
|
||||
/// Whether this node serves as the text container for editable content of
|
||||
/// <input> or <textarea> element.
|
||||
const IS_TEXT_CONTROL_INNER_EDITOR = 1 << 12;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -697,6 +701,16 @@ impl Node {
|
|||
self.flags.get().contains(NodeFlags::IS_CONNECTED)
|
||||
}
|
||||
|
||||
pub(crate) fn set_text_control_inner_editor(&self) {
|
||||
self.set_flag(NodeFlags::IS_TEXT_CONTROL_INNER_EDITOR, true)
|
||||
}
|
||||
|
||||
pub(crate) fn is_text_control_inner_editor(&self) -> bool {
|
||||
self.flags
|
||||
.get()
|
||||
.contains(NodeFlags::IS_TEXT_CONTROL_INNER_EDITOR)
|
||||
}
|
||||
|
||||
/// Returns the type ID of this node.
|
||||
pub(crate) fn type_id(&self) -> NodeTypeId {
|
||||
match *self.eventtarget.type_id() {
|
||||
|
@ -1579,6 +1593,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
|
|||
fn assigned_slot_for_layout(self) -> Option<LayoutDom<'dom, HTMLSlotElement>>;
|
||||
|
||||
fn is_element_for_layout(&self) -> bool;
|
||||
fn is_text_node_for_layout(&self) -> bool;
|
||||
unsafe fn get_flag(self, flag: NodeFlags) -> bool;
|
||||
unsafe fn set_flag(self, flag: NodeFlags, value: bool);
|
||||
|
||||
|
@ -1614,6 +1629,9 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
|
|||
|
||||
/// Whether this element is a `<input>` rendered as text or a `<textarea>`.
|
||||
fn is_text_input(&self) -> bool;
|
||||
|
||||
/// Whether this element serve as a container of editable text for a text input.
|
||||
fn is_text_control_inner_editor(&self) -> bool;
|
||||
fn text_content(self) -> Cow<'dom, str>;
|
||||
fn selection(self) -> Option<Range<usize>>;
|
||||
fn image_url(self) -> Option<ServoUrl>;
|
||||
|
@ -1646,6 +1664,11 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
(*self).is::<Element>()
|
||||
}
|
||||
|
||||
fn is_text_node_for_layout(&self) -> bool {
|
||||
self.type_id_for_layout() ==
|
||||
NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn composed_parent_node_ref(self) -> Option<LayoutDom<'dom, Node>> {
|
||||
let parent = self.parent_node_ref();
|
||||
|
@ -1787,8 +1810,8 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
{
|
||||
let input = self.unsafe_get().downcast::<HTMLInputElement>().unwrap();
|
||||
|
||||
// FIXME: All the non-color input types currently render as text
|
||||
input.input_type() != InputType::Color
|
||||
// FIXME: All the non-color and non-text input types currently render as text
|
||||
!matches!(input.input_type(), InputType::Color | InputType::Text)
|
||||
} else {
|
||||
type_id ==
|
||||
NodeTypeId::Element(ElementTypeId::HTMLElement(
|
||||
|
@ -1797,6 +1820,10 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_text_control_inner_editor(&self) -> bool {
|
||||
self.unsafe_get().is_text_control_inner_editor()
|
||||
}
|
||||
|
||||
fn text_content(self) -> Cow<'dom, str> {
|
||||
if let Some(text) = self.downcast::<Text>() {
|
||||
return text.upcast().data_for_layout().into();
|
||||
|
@ -1814,6 +1841,25 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
|
|||
}
|
||||
|
||||
fn selection(self) -> Option<Range<usize>> {
|
||||
// This node is a text node of a text control inner editor in a <input> or <textarea> element.
|
||||
// So we should find those corresponding element, and get its selection.
|
||||
if self.is_text_node_for_layout() &&
|
||||
self.parent_node_ref()
|
||||
.is_some_and(|parent| parent.is_text_control_inner_editor())
|
||||
{
|
||||
let shadow_root = self.containing_shadow_root_for_layout();
|
||||
if let Some(containing_shadow_host) = shadow_root.map(|root| root.get_host_for_layout())
|
||||
{
|
||||
if let Some(area) = containing_shadow_host.downcast::<HTMLTextAreaElement>() {
|
||||
return area.selection_for_layout();
|
||||
}
|
||||
|
||||
if let Some(input) = containing_shadow_host.downcast::<HTMLInputElement>() {
|
||||
return input.selection_for_layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(area) = self.downcast::<HTMLTextAreaElement>() {
|
||||
return area.selection_for_layout();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue