diff options
author | Martin Robinson <mrobinson@igalia.com> | 2024-06-20 12:13:50 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-20 10:13:50 +0000 |
commit | 44064b14392838fd7da148000b58c9a3cc07d4e7 (patch) | |
tree | 47b931db8d9adfe85518f8a98af68325b1e7c4a7 /components/layout_2020 | |
parent | 3d6accbbe3a33ea5e3c621ae3c291a0f35fcba73 (diff) | |
download | servo-44064b14392838fd7da148000b58c9a3cc07d4e7.tar.gz servo-44064b14392838fd7da148000b58c9a3cc07d4e7.zip |
layout: Add very basic support for showing text in input boxes (#32365)
This only paints text in input fields. Selection and cursor are still
not painted.
In addition to adding this feature, the change also updates the
user-agent.css with the latest from the HTML specification. Extra
padding and extraneous settings (such as a bogus line-height and
min-height) are also removed from servo.css. This leads to some new
passes.
There are some new passes, this introduces failures as inserting text
reveals issues that were hidden before. Notably:
- failures in `/html/editing/editing-0/spelling-and-grammar-checking/`:
We do not support spell-checking.
- Most of the rest of the new failures are missing features of input
boxes that are also missing in legacy layout.
Diffstat (limited to 'components/layout_2020')
-rw-r--r-- | components/layout_2020/dom_traversal.rs | 26 | ||||
-rw-r--r-- | components/layout_2020/flexbox/construct.rs | 3 | ||||
-rw-r--r-- | components/layout_2020/flow/construct.rs | 3 | ||||
-rw-r--r-- | components/layout_2020/flow/inline/construct.rs | 3 | ||||
-rw-r--r-- | components/layout_2020/flow/inline/mod.rs | 69 |
5 files changed, 93 insertions, 11 deletions
diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs index 95497c2b2e3..10ae9ae3978 100644 --- a/components/layout_2020/dom_traversal.rs +++ b/components/layout_2020/dom_traversal.rs @@ -7,6 +7,7 @@ use std::borrow::Cow; use html5ever::{local_name, LocalName}; use log::warn; use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode}; +use script_layout_interface::{LayoutElementType, LayoutNodeType}; use servo_arc::Arc as ServoArc; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; @@ -33,7 +34,7 @@ pub(crate) struct NodeAndStyleInfo<Node> { pub style: ServoArc<ComputedValues>, } -impl<Node> NodeAndStyleInfo<Node> { +impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> { fn new_with_pseudo( node: Node, pseudo_element_type: WhichPseudoElement, @@ -53,6 +54,12 @@ impl<Node> NodeAndStyleInfo<Node> { style, } } + + pub(crate) fn is_single_line_text_input(&self) -> bool { + self.node.map_or(false, |node| { + node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) + }) + } } impl<Node: Clone> NodeAndStyleInfo<Node> { @@ -172,6 +179,23 @@ fn traverse_children_of<'dom, Node>( } } + if matches!( + parent_element.type_id(), + LayoutNodeType::Element(LayoutElementType::HTMLInputElement) + ) { + let info = NodeAndStyleInfo::new(parent_element, parent_element.style(context)); + + // 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: + // <https://drafts.csswg.org/css-ui/#element-with-default-preferred-size>. + // + // TODO: Is there a less hacky way to do this? + handler.handle_text(&info, "\u{200B}".into()); + + handler.handle_text(&info, parent_element.to_threadsafe().node_text_content()); + } + traverse_pseudo_element(WhichPseudoElement::After, parent_element, context, handler); } diff --git a/components/layout_2020/flexbox/construct.rs b/components/layout_2020/flexbox/construct.rs index 1695393065a..a21618d674e 100644 --- a/components/layout_2020/flexbox/construct.rs +++ b/components/layout_2020/flexbox/construct.rs @@ -159,7 +159,8 @@ where let Some(inline_formatting_context) = inline_formatting_context_builder.finish( self.context, self.text_decoration_line, - true, /* has_first_formatted_line */ + true, /* has_first_formatted_line */ + false, /* is_single_line_text_box */ ) else { return None; }; diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs index 3e9cb63b224..5a53bbd12d9 100644 --- a/components/layout_2020/flow/construct.rs +++ b/components/layout_2020/flow/construct.rs @@ -191,6 +191,7 @@ where ) -> Self { let text_decoration_line = propagated_text_decoration_line | info.style.clone_text_decoration_line(); + BlockContainerBuilder { context, info, @@ -214,6 +215,7 @@ where self.context, self.text_decoration_line, !self.have_already_seen_first_line_for_text_indent, + self.info.is_single_line_text_input(), ) { // There are two options here. This block was composed of both one or more inline formatting contexts // and child blocks OR this block was a single inline formatting context. In the latter case, we @@ -598,6 +600,7 @@ where self.context, self.text_decoration_line, !self.have_already_seen_first_line_for_text_indent, + self.info.is_single_line_text_input(), ) { self.push_block_level_job_for_inline_formatting_context(inline_formatting_context); } diff --git a/components/layout_2020/flow/inline/construct.rs b/components/layout_2020/flow/inline/construct.rs index 603a807a62e..31c52d217dd 100644 --- a/components/layout_2020/flow/inline/construct.rs +++ b/components/layout_2020/flow/inline/construct.rs @@ -283,6 +283,7 @@ impl InlineFormattingContextBuilder { layout_context, text_decoration_line, has_first_formatted_line, + /* is_single_line_text_input = */ false, ) } @@ -292,6 +293,7 @@ impl InlineFormattingContextBuilder { layout_context: &LayoutContext, text_decoration_line: TextDecorationLine, has_first_formatted_line: bool, + is_single_line_text_input: bool, ) -> Option<InlineFormattingContext> { if self.is_empty() { return None; @@ -305,6 +307,7 @@ impl InlineFormattingContextBuilder { layout_context, text_decoration_line, has_first_formatted_line, + is_single_line_text_input, )) } } diff --git a/components/layout_2020/flow/inline/mod.rs b/components/layout_2020/flow/inline/mod.rs index 29ef49ab3f7..b43ba37bda8 100644 --- a/components/layout_2020/flow/inline/mod.rs +++ b/components/layout_2020/flow/inline/mod.rs @@ -155,6 +155,9 @@ pub(crate) struct InlineFormattingContext { /// Whether or not this [`InlineFormattingContext`] contains floats. pub(super) contains_floats: bool, + + /// Whether or not this is an inline formatting context for a single line text input. + pub(super) is_single_line_text_input: bool, } /// A collection of data used to cache [`FontMetrics`] in the [`InlineFormattingContext`] @@ -574,10 +577,20 @@ impl UnbreakableSegmentUnderConstruction { } } +bitflags! { + pub struct InlineContainerStateFlags: u8 { + const CREATE_STRUT = 0b0001; + const IS_SINGLE_LINE_TEXT_INPUT = 0b0010; + } +} + struct InlineContainerState { /// The style of this inline container. style: Arc<ComputedValues>, + /// Flags which describe details of this [`InlineContainerState`]. + flags: InlineContainerStateFlags, + /// Whether or not we have processed any content (an atomic element or text) for /// this inline box on the current line OR any previous line. has_content: bool, @@ -1583,6 +1596,7 @@ impl InlineFormattingContext { layout_context: &LayoutContext, text_decoration_line: TextDecorationLine, has_first_formatted_line: bool, + is_single_line_text_input: bool, ) -> Self { // This is to prevent a double borrow. let text_content: String = builder.text_segments.into_iter().collect(); @@ -1622,6 +1636,7 @@ impl InlineFormattingContext { text_decoration_line, has_first_formatted_line, contains_floats: builder.contains_floats, + is_single_line_text_input, } } @@ -1665,6 +1680,15 @@ impl InlineFormattingContext { .map(|font| font.metrics.clone()); let style_text = containing_block.style.get_inherited_text(); + let mut inline_container_state_flags = InlineContainerStateFlags::empty(); + if inline_container_needs_strut(style, layout_context, None) { + inline_container_state_flags.insert(InlineContainerStateFlags::CREATE_STRUT); + } + if self.is_single_line_text_input { + inline_container_state_flags + .insert(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT); + } + let mut ifc = InlineFormattingContextState { positioning_context, containing_block, @@ -1678,10 +1702,10 @@ impl InlineFormattingContext { }), root_nesting_level: InlineContainerState::new( style.to_arc(), + inline_container_state_flags, None, /* parent_container */ self.text_decoration_line, default_font_metrics.as_ref(), - inline_container_needs_strut(style, layout_context, None), ), inline_box_state_stack: Vec::new(), current_line_segment: UnbreakableSegmentUnderConstruction::new(), @@ -1753,14 +1777,18 @@ impl InlineFormattingContext { impl InlineContainerState { fn new( style: Arc<ComputedValues>, + flags: InlineContainerStateFlags, parent_container: Option<&InlineContainerState>, parent_text_decoration_line: TextDecorationLine, font_metrics: Option<&FontMetrics>, - create_strut: bool, ) -> Self { let text_decoration_line = parent_text_decoration_line | style.clone_text_decoration_line(); let font_metrics = font_metrics.cloned().unwrap_or_else(FontMetrics::empty); - let line_height = line_height(&style, &font_metrics); + let line_height = line_height( + &style, + &font_metrics, + flags.contains(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT), + ); let mut baseline_offset = Au::zero(); let mut strut_block_sizes = Self::get_block_sizes_with_style( @@ -1783,12 +1811,13 @@ impl InlineContainerState { let mut nested_block_sizes = parent_container .map(|container| container.nested_strut_block_sizes.clone()) .unwrap_or_else(LineBlockSizes::zero); - if create_strut { + if flags.contains(InlineContainerStateFlags::CREATE_STRUT) { nested_block_sizes.max_assign(&strut_block_sizes); } Self { style, + flags, has_content: false, text_decoration_line, nested_strut_block_sizes: nested_block_sizes, @@ -1877,7 +1906,12 @@ impl InlineContainerState { &self.style, font_metrics, font_metrics_of_first_font, - line_height(&self.style, font_metrics), + line_height( + &self.style, + font_metrics, + self.flags + .contains(InlineContainerStateFlags::IS_SINGLE_LINE_TEXT_INPUT), + ), ) } @@ -1945,14 +1979,19 @@ impl InlineBoxContainerState { ) -> Self { let style = inline_box.style.clone(); let pbm = style.padding_border_margin(containing_block); - let create_strut = inline_container_needs_strut(&style, layout_context, Some(&pbm)); + + let mut flags = InlineContainerStateFlags::empty(); + if inline_container_needs_strut(&style, layout_context, Some(&pbm)) { + flags.insert(InlineContainerStateFlags::CREATE_STRUT); + } + Self { base: InlineContainerState::new( style, + flags, Some(parent_container), parent_container.text_decoration_line, font_metrics, - create_strut, ), base_fragment_info: inline_box.base_fragment_info, pbm, @@ -2222,14 +2261,26 @@ fn place_pending_floats(ifc: &mut InlineFormattingContextState, line_items: &mut } } -fn line_height(parent_style: &ComputedValues, font_metrics: &FontMetrics) -> Length { +fn line_height( + parent_style: &ComputedValues, + font_metrics: &FontMetrics, + is_single_line_text_input: bool, +) -> Length { let font = parent_style.get_font(); let font_size = font.font_size.computed_size(); - match font.line_height { + let mut line_height = match font.line_height { LineHeight::Normal => Length::from(font_metrics.line_gap), LineHeight::Number(number) => font_size * number.0, LineHeight::Length(length) => length.0, + }; + + // Single line text inputs line height is clamped to the size of `normal`. See + // <https://github.com/whatwg/html/pull/5462>. + if is_single_line_text_input { + line_height.max_assign(font_metrics.line_gap.into()); } + + line_height } fn effective_vertical_align( |