Use NodeFlag for marking and fix caret color

Signed-off-by: stevennovaryo <steven.novaryo@gmail.com>
This commit is contained in:
stevennovaryo 2025-05-20 19:45:10 +08:00
parent f638c05710
commit 335a1d7ad2
7 changed files with 53 additions and 40 deletions

View file

@ -539,8 +539,7 @@ impl InspectorHighlight {
});
// We expect all fragments generated by one node to be in the same scroll tree node and clip node
// MYNOTES: Error for Input type text shadow DOM.
// debug_assert_eq!(spatial_id, state.spatial_id);
debug_assert_eq!(spatial_id, state.spatial_id);
if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
debug_assert_eq!(
clip_chain_id, state.clip_chain_id,

View file

@ -59,8 +59,10 @@ impl<'dom> NodeAndStyleInfo<'dom> {
}
}
// Whether this is a container for the editable text within a single-line text input.
pub(crate) fn is_single_line_text_input(&self) -> bool {
self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement)
self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) ||
self.node.is_text_editing_root()
}
pub(crate) fn pseudo(

View file

@ -4,8 +4,6 @@ button {
button,
input {
padding-block: 2px;
padding-inline: 1px;
background: white;
border: solid lightgrey 1px;
color: black;

View file

@ -865,13 +865,6 @@ impl LayoutDom<'_, Element> {
pub(super) fn focus_state(self) -> bool {
self.unsafe_get().state.get().contains(ElementState::FOCUS)
}
pub(super) fn text_editing_root(self) -> bool {
self.unsafe_get()
.state
.get()
.contains(ElementState::TEXT_EDITING_ROOT)
}
}
impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> {
@ -4886,10 +4879,6 @@ impl Element {
self.set_state(ElementState::FULLSCREEN, value)
}
pub(crate) fn set_text_editing_root_state(&self, value: bool) {
self.set_state(ElementState::TEXT_EDITING_ROOT, value)
}
/// <https://dom.spec.whatwg.org/#connected>
pub(crate) fn is_connected(&self) -> bool {
self.upcast::<Node>().is_connected()

View file

@ -119,14 +119,21 @@ struct InputTypeColorShadowTree {
color_value: Dom<HTMLDivElement>,
}
// FIXME: These styles should be inside UA stylesheet,
// but we should support pseudo element first.
// FIXME: These styles should be inside UA stylesheet, but it is not possible without internal pseudo element support.
// FIXME: We are setting `pointer-events: none;` because focus is not propagated to its ancestor.
// FIXME: We are using `position: absolute` to put place the editing root and placeholder
// on top of each other, but this will create a unnecessary element in between.
const TEXT_TREE_STYLE: &str = "
#input-editing-root::selection, #input-placeholder::selection {
background: rgba(176, 214, 255, 1.0);
color: black;
}
#input-container {
position: relative;
pointer-events: none;
}
#input-editing-root, #input-placeholder {
overflow-wrap: normal;
white-space: pre;
@ -134,6 +141,7 @@ const TEXT_TREE_STYLE: &str = "
}
#input-placeholder {
position: absolute;
color: grey;
overflow: hidden;
}
@ -1113,18 +1121,22 @@ impl HTMLInputElement {
let shadow_root = self.shadow_root(can_gc);
Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
let inner_container =
HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
inner_container
.upcast::<Element>()
.SetId(DOMString::from("input-container"), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(inner_container.upcast::<Node>(), can_gc)
.unwrap();
let placeholder_container =
HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
placeholder_container
.upcast::<Element>()
.SetId(DOMString::from("input-placeholder"), can_gc);
// MYNOTES:
// This is not a text editing root, but we should do this to show it's
// editing caret when placeholder is still visible
placeholder_container
.upcast::<Element>()
.set_text_editing_root_state(true);
shadow_root
inner_container
.upcast::<Node>()
.AppendChild(placeholder_container.upcast::<Node>(), can_gc)
.unwrap();
@ -1136,9 +1148,9 @@ impl HTMLInputElement {
// We should probably use pseudo element to check this.
// Chrome is using (private?) element attrs,
text_container
.upcast::<Element>()
.set_text_editing_root_state(true);
shadow_root
.upcast::<Node>()
.set_text_editing_root();
inner_container
.upcast::<Node>()
.AppendChild(text_container.upcast::<Node>(), can_gc)
.unwrap();
@ -1265,10 +1277,14 @@ impl HTMLInputElement {
let placeholder_text = match (value.is_empty(), self.placeholder.borrow().is_empty()) {
(true, false) => self.placeholder.to_owned().take(),
(true, true) => "\u{200B}".into(),
_ => DOMString::new(),
};
let value_text = match value.is_empty() {
false => value,
true => "\u{200B}".into(),
};
text_shadow_tree
.placeholder_container
.upcast::<Node>()
@ -1276,7 +1292,7 @@ impl HTMLInputElement {
text_shadow_tree
.text_container
.upcast::<Node>()
.SetTextContent(Some(value), can_gc);
.SetTextContent(Some(value_text), can_gc);
}
if self.input_type() == InputType::Color {
let color_shadow_tree = self.color_shadow_tree(can_gc);

View file

@ -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 has a serve as the text container for editable content of
/// <input> or <textarea> element.
const IS_TEXT_EDITING_ROOT = 1 << 13;
}
}
@ -697,6 +701,14 @@ impl Node {
self.flags.get().contains(NodeFlags::IS_CONNECTED)
}
pub(crate) fn set_text_editing_root(&self) {
self.set_flag(NodeFlags::IS_TEXT_EDITING_ROOT, true)
}
pub(crate) fn is_text_editing_root(&self) -> bool {
self.flags.get().contains(NodeFlags::IS_TEXT_EDITING_ROOT)
}
/// Returns the type ID of this node.
pub(crate) fn type_id(&self) -> NodeTypeId {
match *self.eventtarget.type_id() {
@ -1619,7 +1631,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
fn is_text_input_with_shadow_dom(&self) -> bool;
/// Whether this element serve as a container of editable text for a text input.
fn text_editing_root(&self) -> bool;
fn is_text_editing_root(&self) -> bool;
fn text_content(self) -> Cow<'dom, str>;
fn selection(self) -> Option<Range<usize>>;
fn image_url(self) -> Option<ServoUrl>;
@ -1818,11 +1830,8 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
}
}
fn text_editing_root(&self) -> bool {
match self.downcast::<Element>() {
Some(element) => element.text_editing_root(),
_ => false,
}
fn is_text_editing_root(&self) -> bool {
self.unsafe_get().is_text_editing_root()
}
fn text_content(self) -> Cow<'dom, str> {
@ -1844,7 +1853,7 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
fn selection(self) -> Option<Range<usize>> {
// This container is a text editing root of a <input> or <textarea> element.
// So we should find those corresponding element, and get its selection.
if self.text_editing_root() {
if self.is_text_editing_root() {
let mut maybe_parent_node = self.composed_parent_node_ref();
while let Some(parent_node) = maybe_parent_node {
if let Some(area) = parent_node.downcast::<HTMLTextAreaElement>() {

View file

@ -102,7 +102,7 @@ impl<'dom> ServoLayoutNode<'dom> {
}
pub fn is_text_editing_root(&self) -> bool {
self.node.text_editing_root()
self.node.is_text_editing_root()
}
}