Create shadow dom for text

Signed-off-by: stevennovaryo <steven.novaryo@gmail.com>
This commit is contained in:
stevennovaryo 2025-05-15 21:55:41 +08:00
parent 23ce7b31ac
commit 4e44ecafd2
4 changed files with 125 additions and 5 deletions

View file

@ -539,7 +539,8 @@ impl InspectorHighlight {
});
// We expect all fragments generated by one node to be in the same scroll tree node and clip node
debug_assert_eq!(spatial_id, state.spatial_id);
// MYNOTES: Error for Input type text shadow DOM.
// 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

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

View file

@ -101,6 +101,14 @@ const DEFAULT_RESET_VALUE: &str = "Reset";
const PASSWORD_REPLACEMENT_CHAR: char = '●';
const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
/// MYNOTES: document this and check name later.
struct InputTypeTextShadowTree {
text_container: Dom<HTMLDivElement>,
placeholder_container: Dom<HTMLDivElement>,
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
/// Contains references to the elements in the shadow tree for `<input type=range>`.
@ -111,10 +119,30 @@ struct InputTypeColorShadowTree {
color_value: Dom<HTMLDivElement>,
}
const TEXT_TREE_STYLE: &str = "
#input-editing-root, #input-placeholder {
scrollbar-width: none;
resize: none;
word-wrap: normal;
white-space: pre;
}
#input-placeholder {
color: grey;
overflow: hidden;
pointer-events: none;
user-select: none;
direction: inherit;
text-orientation: inherit;
writing-mode: inherit;
}
";
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[non_exhaustive]
enum ShadowTree {
Text(InputTypeTextShadowTree),
Color(InputTypeColorShadowTree),
// TODO: Add shadow trees for other input types (range etc) here
}
@ -1079,6 +1107,77 @@ impl HTMLInputElement {
})
}
fn create_text_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let shadow_root = self.shadow_root(can_gc);
Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
let placeholder_container = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
placeholder_container
.upcast::<Element>()
.SetId(DOMString::from("input-placeholder"), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(placeholder_container.upcast::<Node>(), can_gc)
.unwrap();
let text_container = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
text_container
.upcast::<Element>()
.SetId(DOMString::from("input-editing-root"), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(text_container.upcast::<Node>(), can_gc)
.unwrap();
let style = HTMLStyleElement::new(
local_name!("style"),
None,
&document,
None,
ElementCreator::ScriptCreated,
can_gc,
);
style
.upcast::<Node>()
.SetTextContent(Some(DOMString::from(TEXT_TREE_STYLE)), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(style.upcast::<Node>(), can_gc)
.unwrap();
let _ = self
.shadow_tree
.borrow_mut()
.insert(ShadowTree::Text(InputTypeTextShadowTree {
text_container: text_container.as_traced(),
placeholder_container: placeholder_container.as_traced(),
}));
}
fn text_shadow_tree(&self, can_gc: CanGc) -> Ref<InputTypeTextShadowTree> {
let has_text_shadow_tree = self
.shadow_tree
.borrow()
.as_ref()
.is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Text(_)));
if !has_text_shadow_tree {
self.create_text_shadow_tree(can_gc);
}
let shadow_tree = self.shadow_tree.borrow();
// MYNOTES: will check again for shadow tree getter.
Ref::filter_map(shadow_tree, |shadow_tree| {
let shadow_tree = shadow_tree.as_ref()?;
match shadow_tree {
ShadowTree::Text(text_tree) => Some(text_tree),
_ => None,
}
})
.ok()
.expect("UA shadow tree was not created")
}
fn create_color_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let shadow_root = self.shadow_root(can_gc);
@ -1134,16 +1233,32 @@ impl HTMLInputElement {
}
let shadow_tree = self.shadow_tree.borrow();
// MYNOTES: will check again for shadow tree getter.
Ref::filter_map(shadow_tree, |shadow_tree| {
let shadow_tree = shadow_tree.as_ref()?;
let ShadowTree::Color(color_tree) = shadow_tree;
Some(color_tree)
match shadow_tree {
ShadowTree::Color(color_tree) => Some(color_tree),
_ => None,
}
})
.ok()
.expect("UA shadow tree was not created")
}
fn update_shadow_tree_if_needed(&self, can_gc: CanGc) {
if self.input_type() == InputType::Text {
let text_shadow_tree = self.text_shadow_tree(can_gc);
let value = self.Value();
let placeholder_text = if value.len() == 0 {
self.placeholder.to_owned().take()
} else {
DOMString::new()
};
text_shadow_tree.placeholder_container.upcast::<Node>().SetTextContent(Some(placeholder_text), can_gc);
text_shadow_tree.text_container.upcast::<Node>().SetTextContent(Some(value), can_gc);
}
if self.input_type() == InputType::Color {
let color_shadow_tree = self.color_shadow_tree(can_gc);
let mut value = self.Value();
@ -1266,6 +1381,7 @@ impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElem
self.unsafe_get().size.get()
}
// MYNOTES is implemented for text
fn selection_for_layout(self) -> Option<Range<usize>> {
if !self.upcast::<Element>().focus_state() {
return None;

View file

@ -1787,8 +1787,9 @@ 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
input.input_type() != InputType::Color &&
input.input_type() != InputType::Text
} else {
type_id ==
NodeTypeId::Element(ElementTypeId::HTMLElement(