diff --git a/components/layout/dom_traversal.rs b/components/layout/dom_traversal.rs index 4da6aa48b55..8bf6d919fa3 100644 --- a/components/layout/dom_traversal.rs +++ b/components/layout/dom_traversal.rs @@ -59,14 +59,8 @@ impl<'dom> NodeAndStyleInfo<'dom> { } } - /// Whether this is a container for the editable text within a single-line text input. - /// This is used to solve the special case of line height for a text editor. - /// - // FIXME(stevennovaryo): Now, this would also refer to HTMLInputElement, to handle input - // elements without shadow DOM. pub(crate) fn is_single_line_text_input(&self) -> bool { - self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) || - self.node.is_text_control_inner_editor() + self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) } pub(crate) fn pseudo( diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 68efa07537c..fd4faf0ee43 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -1556,14 +1556,11 @@ 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(target_el.as_deref(), FocusInitiator::Local, can_gc); + self.request_focus(Some(&*el), 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 ed4c37c4378..961fb92121d 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -1703,27 +1703,6 @@ 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::().is_text_control_inner_editor() { - let containing_shadow_host = self.containing_shadow_root().map(|root| root.Host()); - 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 ee61421dfe8..e914ad7f5d1 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -101,29 +101,6 @@ 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. -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 ``. @@ -134,49 +111,10 @@ struct InputTypeColorShadowTree { color_value: Dom, } -// FIXME: These styles should be inside UA stylesheet, but it is not possible without internal pseudo element support. -const TEXT_TREE_STYLE: &str = " -#input-editor::selection { - background: rgba(176, 214, 255, 1.0); - color: black; -} - -:host:not(:placeholder-shown) #input-placeholder { - visibility: hidden !important -} - -#input-editor { - overflow-wrap: normal; - pointer-events: auto; -} - -#input-container { - position: relative; - height: 100%; - pointer-events: none; - display: flex; -} - -#input-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; - pointer-events: none !important; -} -"; - #[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 } @@ -1133,7 +1071,7 @@ impl HTMLInputElement { ShadowRootMode::Closed, false, false, - true, + false, SlotAssignmentMode::Manual, can_gc, ) @@ -1141,92 +1079,6 @@ 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::(), can_gc); - - let inner_container = - HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc); - inner_container - .upcast::() - .SetId(DOMString::from("input-container"), can_gc); - shadow_root - .upcast::() - .AppendChild(inner_container.upcast::(), can_gc) - .unwrap(); - - let placeholder_container = - HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc); - placeholder_container - .upcast::() - .SetId(DOMString::from("input-placeholder"), can_gc); - inner_container - .upcast::() - .AppendChild(placeholder_container.upcast::(), can_gc) - .unwrap(); - - let text_container = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc); - text_container - .upcast::() - .SetId(DOMString::from("input-editor"), can_gc); - text_container - .upcast::() - .set_text_control_inner_editor(); - inner_container - .upcast::() - .AppendChild(text_container.upcast::(), can_gc) - .unwrap(); - - let style = HTMLStyleElement::new( - local_name!("style"), - None, - &document, - None, - ElementCreator::ScriptCreated, - can_gc, - ); - // TODO(stevennovaryo): Either use UA stylesheet with internal pseudo element or preemptively parse - // the stylesheet to reduce the costly operation and avoid CSP related error. - style - .upcast::() - .SetTextContent(Some(DOMString::from(TEXT_TREE_STYLE)), can_gc); - shadow_root - .upcast::() - .AppendChild(style.upcast::(), 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 { - 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); @@ -1284,53 +1136,27 @@ impl HTMLInputElement { 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::Color(color_tree) => Some(color_tree), - _ => None, - } + let ShadowTree::Color(color_tree) = shadow_tree; + Some(color_tree) }) .ok() .expect("UA shadow tree was not created") } fn update_shadow_tree_if_needed(&self, can_gc: CanGc) { - match 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: Is there a less hacky way to do this? - let value_text = match value.is_empty() { - false => value, - true => "\u{200B}".into(), - }; - - text_shadow_tree - .text_container - .upcast::() - .SetTextContent(Some(value_text), can_gc); - }, - 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); - }, - _ => {}, + 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); } } } @@ -1639,29 +1465,22 @@ 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(); + let mut textinput = self.textinput.borrow_mut(); + + // Step 5. + if *textinput.single_line_content() != value { + // Steps 1-2 + textinput.set_content(value); // Step 5. - if *textinput.single_line_content() != value { - // Steps 1-2 - textinput.set_content(value); - - // Step 5. - textinput.clear_selection_to_limit(Direction::Forward); - } + textinput.clear_selection_to_limit(Direction::Forward); } - - // Additionaly, update the placeholder shown state. This is - // normally being done in the attributed mutated. And, being - // done in another scope to prevent borrow checker issues. - self.update_placeholder_shown_state(); }, ValueMode::Default | ValueMode::DefaultOn => { self.upcast::() @@ -2244,19 +2063,6 @@ 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; - } - - self.text_shadow_tree(can_gc) - .placeholder_container - .upcast::() - .SetTextContent(Some(self.placeholder.borrow().clone()), can_gc); - } - // https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file) // Select files by invoking UI or by passed in argument fn select_files(&self, opt_test_paths: Option>, can_gc: CanGc) { @@ -2882,11 +2688,8 @@ impl VirtualMethods for HTMLInputElement { }, } - self.update_text_shadow_tree_placeholder(can_gc); self.update_placeholder_shown_state(); }, - // 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); @@ -2935,7 +2738,6 @@ impl VirtualMethods for HTMLInputElement { .extend(attr.value().chars().filter(|&c| c != '\n' && c != '\r')); } } - self.update_text_shadow_tree_placeholder(can_gc); self.update_placeholder_shown_state(); }, local_name!("readonly") => { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 4a0aec45024..bf36242572f 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -230,10 +230,6 @@ 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 - /// or