mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
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.
This commit is contained in:
parent
3d6accbbe3
commit
44064b1439
65 changed files with 225 additions and 204 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue