diff --git a/Cargo.lock b/Cargo.lock index b5b5d5161ef..bd5d81a19c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7249,7 +7249,7 @@ dependencies = [ [[package]] name = "selectors" version = "0.30.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "bitflags 2.9.1", "cssparser", @@ -7555,7 +7555,7 @@ dependencies = [ [[package]] name = "servo_arc" version = "0.4.1" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "serde", "stable_deref_trait", @@ -8017,7 +8017,7 @@ dependencies = [ [[package]] name = "stylo" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "app_units", "arrayvec", @@ -8074,7 +8074,7 @@ dependencies = [ [[package]] name = "stylo_atoms" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "string_cache", "string_cache_codegen", @@ -8083,12 +8083,12 @@ dependencies = [ [[package]] name = "stylo_config" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" [[package]] name = "stylo_derive" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "darling", "proc-macro2", @@ -8100,7 +8100,7 @@ dependencies = [ [[package]] name = "stylo_dom" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "bitflags 2.9.1", "stylo_malloc_size_of", @@ -8109,7 +8109,7 @@ dependencies = [ [[package]] name = "stylo_malloc_size_of" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "app_units", "cssparser", @@ -8126,12 +8126,12 @@ dependencies = [ [[package]] name = "stylo_static_prefs" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" [[package]] name = "stylo_traits" version = "0.5.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "app_units", "bitflags 2.9.1", @@ -8548,7 +8548,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "to_shmem" version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "cssparser", "servo_arc", @@ -8561,7 +8561,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-07-01#95cc620f5a5fadf2e4bdacb17e4731c835ab381b" +source = "git+https://github.com/servo/stylo?branch=2025-07-01#14c096bd61af12a7c9bd4e48d6696cc8b16feb8b" dependencies = [ "darling", "proc-macro2", diff --git a/components/layout/dom_traversal.rs b/components/layout/dom_traversal.rs index 9d09795c978..57bf35f2f8e 100644 --- a/components/layout/dom_traversal.rs +++ b/components/layout/dom_traversal.rs @@ -51,8 +51,16 @@ impl<'dom> NodeAndStyleInfo<'dom> { } } + /// Whether this is a container for the text within a single-line text input. This + /// is used to solve the special case of line height for a text entry widget. + /// + // TODO(stevennovaryo): Remove the addition of HTMLInputElement here once all of the + // input element is implemented with UA shadow DOM. This is temporary + // workaround for past version of input element where we are + // rendering it as a bare html element. 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_container_of_single_line_input() } pub(crate) fn pseudo( @@ -75,7 +83,18 @@ impl<'dom> NodeAndStyleInfo<'dom> { } pub(crate) fn get_selected_style(&self) -> ServoArc { - self.node.to_threadsafe().selected_style() + // This is a workaround for handling the `::selection` pseudos where it would not + // propagate to the children and Shadow DOM elements. For this case, UA widget + // inner elements should follow the originating element in terms of selection. + if self.node.is_in_ua_widget() { + self.node + .containing_shadow_host() + .expect("Ua widget inner editor is not contained") + .to_threadsafe() + .selected_style() + } else { + self.node.to_threadsafe().selected_style() + } } pub(crate) fn get_selection_range(&self) -> Option> { @@ -194,16 +213,13 @@ fn traverse_children_of<'dom>( ) { traverse_eager_pseudo_element(PseudoElement::Before, parent_element_info, context, handler); + // TODO(stevennovaryo): In the past we are rendering text input as a normal element, + // and the processing of text is happening here. Remove this + // special case after the implementation of UA Shadow DOM for + // all affected input elements. if parent_element_info.node.is_text_input() { let node_text_content = parent_element_info.node.to_threadsafe().node_text_content(); if node_text_content.is_empty() { - // The addition of zero-width space here forces the text input to have an inline formatting - // context that might otherwise be trimmed if there's no text. This is important to ensure - // that the input element is at least as tall as the line gap of the caret: - // . - // - // This is also used to ensure that the caret will still be rendered when the input is empty. - // TODO: Is there a less hacky way to do this? handler.handle_text(parent_element_info, "\u{200B}".into()); } else { handler.handle_text(parent_element_info, node_text_content); diff --git a/components/layout/flow/inline/mod.rs b/components/layout/flow/inline/mod.rs index 7326ca2610d..14405e4474c 100644 --- a/components/layout/flow/inline/mod.rs +++ b/components/layout/flow/inline/mod.rs @@ -168,7 +168,8 @@ pub(crate) struct InlineFormattingContext { /// Whether or not this [`InlineFormattingContext`] contains floats. pub(super) contains_floats: bool, - /// Whether or not this is an [`InlineFormattingContext`] for a single line text input. + /// Whether or not this is an [`InlineFormattingContext`] for a single line text input's inner + /// text container. pub(super) is_single_line_text_input: bool, /// Whether or not this is an [`InlineFormattingContext`] has right-to-left content, which @@ -2237,8 +2238,9 @@ fn line_height( LineHeight::Length(length) => length.0.into(), }; - // Single line text inputs line height is clamped to the size of `normal`. See - // . + // The line height of a single-line text input's inner text container is clamped to + // the size of `normal`. + // if is_single_line_text_input { line_height.max_assign(font_metrics.line_gap); } diff --git a/components/layout/stylesheets/servo.css b/components/layout/stylesheets/servo.css index c3ad5b77e87..239de221d26 100644 --- a/components/layout/stylesheets/servo.css +++ b/components/layout/stylesheets/servo.css @@ -19,6 +19,36 @@ textarea { font-size: 0.8333em; } +input::-servo-text-control-inner-editor { + overflow-wrap: normal; + pointer-events: auto; +} + +input::-servo-text-control-inner-container { + position: relative; + height: 100%; + pointer-events: none; + display: flex; +} + +input:not(:placeholder-shown)::placeholder { + visibility: hidden !important +} + +input::-servo-text-control-inner-editor, input::placeholder { + white-space: pre; + margin-block: auto !important; + inset-block: 0 !important; + block-size: fit-content !important; +} + +input::placeholder { + overflow: hidden !important; + position: absolute !important; + color: grey !important; + pointer-events: none !important; +} + input::color-swatch { width: 100%; height: 100%; diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 6c7ad1deb3c..8c06d6de428 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -1556,11 +1556,14 @@ impl Document { return; } + // For a node within a text input UA shadow DOM, delegate the focus target into its shadow host. + // TODO: This focus delegation should be done with shadow DOM delegateFocus attribute. + let target_el = el.find_focusable_shadow_host_if_necessary(); + self.begin_focus_transaction(); - // Try to focus `el`. If it's not focusable, focus the document - // instead. + // Try to focus `el`. If it's not focusable, focus the document instead. self.request_focus(None, FocusInitiator::Local, can_gc); - self.request_focus(Some(&*el), FocusInitiator::Local, can_gc); + self.request_focus(target_el.as_deref(), FocusInitiator::Local, can_gc); } let dom_event = DomRoot::upcast::(MouseEvent::for_platform_mouse_event( diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 374ea1a7197..05ae67f9d4d 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -1764,6 +1764,29 @@ impl Element { ) } + /// Returns the focusable shadow host if this is a text control inner editor. + /// This is a workaround for the focus delegation of shadow DOM and should be + /// used only to delegate focusable inner editor of [HTMLInputElement] and + /// [HTMLTextAreaElement]. + pub(crate) fn find_focusable_shadow_host_if_necessary(&self) -> Option> { + if self.is_focusable_area() { + Some(DomRoot::from_ref(self)) + } else if self.upcast::().implemented_pseudo_element() == + Some(PseudoElement::ServoTextControlInnerEditor) + { + let containing_shadow_host = self.containing_shadow_root().map(|root| root.Host()); + debug_assert!( + containing_shadow_host + .as_ref() + .is_some_and(|e| e.is_focusable_area()), + "Containing shadow host is not focusable" + ); + containing_shadow_host + } else { + None + } + } + pub(crate) fn is_actually_disabled(&self) -> bool { let node = self.upcast::(); match node.type_id() { diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 405a79f51a2..377828d2f07 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -29,6 +29,8 @@ use js::rust::{HandleObject, MutableHandleObject}; use net_traits::blob_url_store::get_blob_origin; use net_traits::filemanager_thread::{FileManagerResult, FileManagerThreadMsg}; use net_traits::{CoreResourceMsg, IpcSend}; +use script_bindings::codegen::GenericBindings::CharacterDataBinding::CharacterDataMethods; +use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods; use style::attr::AttrValue; use style::selector_parser::PseudoElement; use style::str::{split_commas, str_join}; @@ -79,6 +81,7 @@ use crate::dom::node::{ use crate::dom::nodelist::NodeList; use crate::dom::shadowroot::ShadowRoot; use crate::dom::textcontrol::{TextControlElement, TextControlSelection}; +use crate::dom::types::CharacterData; use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor}; use crate::dom::validitystate::{ValidationFlags, ValidityState}; use crate::dom::virtualmethods::VirtualMethods; @@ -98,6 +101,31 @@ 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)] +/// Contains reference to text control inner editor and placeholder container element in the UA +/// shadow tree for ``. The following is the structure of the shadow tree. +/// +/// ``` +/// +/// #shadow-root +///
+///
+///
+///
+/// +/// ``` +// TODO(stevennovaryo): We are trying to use CSS to mimic Chrome and Firefox's layout for the element. +// 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. +struct InputTypeTextShadowTree { + text_container: Dom, + placeholder_container: Dom, +} + #[derive(Clone, JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] /// Contains references to the elements in the shadow tree for ``. @@ -112,12 +140,13 @@ struct InputTypeColorShadowTree { #[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 } /// -#[derive(Clone, Copy, Default, JSTraceable, PartialEq)] +#[derive(Clone, Copy, Debug, Default, JSTraceable, PartialEq)] #[allow(dead_code)] #[derive(MallocSizeOf)] pub(crate) enum InputType { @@ -1050,12 +1079,98 @@ impl HTMLInputElement { /// Return a reference to the ShadowRoot that this element is a host of, /// or create one if none exists. + // FIXME(stevennovaryo): We should encapsulate the logics for the initiation and maintainance of + // form UA widget inside another struct. fn shadow_root(&self, can_gc: CanGc) -> DomRoot { self.upcast::() .shadow_root() .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); + Node::replace_all(None, shadow_root.upcast::(), can_gc); + + let inner_container = + HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc); + shadow_root + .upcast::() + .AppendChild(inner_container.upcast::(), can_gc) + .unwrap(); + inner_container + .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( + &document, + inner_container.upcast::(), + PseudoElement::ServoTextControlInnerEditor, + can_gc, + ); + + 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 { + 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(); + 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); @@ -1097,27 +1212,69 @@ impl HTMLInputElement { let shadow_tree = self.shadow_tree.borrow(); 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_text_shadow_tree_if_needed(&self, can_gc: CanGc) { + // Should only do this for `type=text` input. + debug_assert_eq!(self.input_type(), InputType::Text); + + let text_shadow_tree = self.text_shadow_tree(can_gc); + let value = self.Value(); + + // The addition of zero-width space here forces the text input to have an inline formatting + // context that might otherwise be trimmed if there's no text. This is important to ensure + // that the input element is at least as tall as the line gap of the caret: + // . + // + // This is also used to ensure that the caret will still be rendered when the input is empty. + // TODO: Could append `
` element to prevent collapses and avoid this hack, but we would + // need to fix the rendering of caret beforehand. + let value_text = match value.is_empty() { + false => value, + true => "\u{200B}".into(), + }; + + // FIXME(stevennovaryo): Refactor this inside a TextControl wrapper + text_shadow_tree + .text_container + .upcast::() + .GetFirstChild() + .expect("Text container without child") + .downcast::() + .expect("First child is not a CharacterData node") + .SetData(value_text); + } + + fn update_color_shadow_tree_if_needed(&self, can_gc: CanGc) { + // Should only do this for `type=color` input. + debug_assert_eq!(self.input_type(), InputType::Color); + + let color_shadow_tree = self.color_shadow_tree(can_gc); + let mut value = self.Value(); + if value.str().is_valid_simple_color_string() { + value.make_ascii_lowercase(); + } else { + value = DOMString::from("#000000"); + } + let style = format!("background-color: {value}"); + color_shadow_tree + .color_value + .upcast::() + .set_string_attribute(&local_name!("style"), style.into(), can_gc); + } + fn update_shadow_tree_if_needed(&self, can_gc: CanGc) { - if self.input_type() == InputType::Color { - let color_shadow_tree = self.color_shadow_tree(can_gc); - let mut value = self.Value(); - if value.str().is_valid_simple_color_string() { - value.make_ascii_lowercase(); - } else { - value = DOMString::from("#000000"); - } - let style = format!("background-color: {value}"); - color_shadow_tree - .color_value - .upcast::() - .set_string_attribute(&local_name!("style"), style.into(), can_gc); + match self.input_type() { + InputType::Text => self.update_text_shadow_tree_if_needed(can_gc), + InputType::Color => self.update_color_shadow_tree_if_needed(can_gc), + _ => {}, } } } @@ -1426,22 +1583,29 @@ impl HTMLInputElementMethods for HTMLInputElement { fn SetValue(&self, mut value: DOMString, can_gc: CanGc) -> ErrorResult { match self.value_mode() { ValueMode::Value => { - // Step 3. - self.value_dirty.set(true); + { + // Step 3. + self.value_dirty.set(true); - // Step 4. - self.sanitize_value(&mut value); + // Step 4. + self.sanitize_value(&mut value); - let mut textinput = self.textinput.borrow_mut(); - - // Step 5. - if *textinput.single_line_content() != value { - // Steps 1-2 - textinput.set_content(value); + let mut textinput = self.textinput.borrow_mut(); // Step 5. - textinput.clear_selection_to_limit(Direction::Forward); + if *textinput.single_line_content() != value { + // Steps 1-2 + textinput.set_content(value); + + // Step 5. + textinput.clear_selection_to_limit(Direction::Forward); + } } + + // Additionally, update the placeholder shown state in another + // scope to prevent the borrow checker issue. This is normally + // being done in the attributed mutated. + self.update_placeholder_shown_state(); }, ValueMode::Default | ValueMode::DefaultOn => { self.upcast::() @@ -2024,6 +2188,26 @@ impl HTMLInputElement { el.set_placeholder_shown_state(has_placeholder && !has_value); } + // Update the placeholder text in the text shadow tree. + // To increase the performance, we would only do this when it is necessary. + fn update_text_shadow_tree_placeholder(&self, can_gc: CanGc) { + if self.input_type() != InputType::Text { + return; + } + + let placeholder_text = self.placeholder.borrow().clone(); + + // FIXME(stevennovaryo): Refactor this inside a TextControl wrapper + self.text_shadow_tree(can_gc) + .placeholder_container + .upcast::() + .GetFirstChild() + .expect("Text container without child") + .downcast::() + .expect("First child is not a CharacterData node") + .SetData(placeholder_text); + } + // https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file) // Select files by invoking UI or by passed in argument pub(crate) fn select_files( @@ -2657,7 +2841,10 @@ impl VirtualMethods for HTMLInputElement { } self.update_placeholder_shown_state(); + self.update_text_shadow_tree_placeholder(can_gc); }, + // FIXME(stevennovaryo): This is only reachable by Default and DefaultOn value mode. While others + // are being handled in [Self::SetValue]. Should we merge this two together? local_name!("value") if !self.value_dirty.get() => { let value = mutation.new_value(attr).map(|value| (**value).to_owned()); let mut value = value.map_or(DOMString::new(), DOMString::from); @@ -2707,6 +2894,7 @@ impl VirtualMethods for HTMLInputElement { } } self.update_placeholder_shown_state(); + self.update_text_shadow_tree_placeholder(can_gc); }, local_name!("readonly") => { if self.input_type().is_textual() { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index f4e870b2e61..98afce97e5b 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -1625,6 +1625,7 @@ pub(crate) trait LayoutNodeHelpers<'dom> { fn assigned_slot_for_layout(self) -> Option>; 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); @@ -1659,7 +1660,19 @@ pub(crate) trait LayoutNodeHelpers<'dom> { unsafe fn clear_style_and_layout_data(self); /// Whether this element is a `` rendered as text or a `