layout: Add line height from preserved segment breaks in quirks mode (#31419)

In quirks mode, preserved segment breaks should add line height to
lines. This matches the behavior of WebKit and Blink, but not Gecko.

This also handles the special-case of `<br>` elements, which are
implemented with preserved segment breaks via `white-space: pre-line`.
This is an implementation detail though because `<br>` has a special
behavior if the line isn't empty -- it doesn't add any line height in
this case.
This commit is contained in:
Martin Robinson 2024-02-26 18:26:41 +01:00 committed by GitHub
parent a9a7e8a5cf
commit e5598590ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 158 additions and 10 deletions

View file

@ -4,7 +4,7 @@
use std::borrow::Cow;
use html5ever::LocalName;
use html5ever::{local_name, LocalName};
use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use servo_arc::Arc as ServoArc;
use style::properties::ComputedValues;
@ -79,6 +79,9 @@ where
Some(element) if element.is_body_element_of_html_element_root() => {
FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT
},
Some(element) if element.get_local_name() == &local_name!("br") => {
FragmentFlags::IS_BR_ELEMENT
},
_ => FragmentFlags::empty(),
};

View file

@ -35,7 +35,7 @@ use crate::flow::float::{FloatBox, SequentialLayoutState};
use crate::flow::FlowLayout;
use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
use crate::fragment_tree::{
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment,
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, FragmentFlags,
PositioningFragment,
};
use crate::geom::{LogicalRect, LogicalVec2};
@ -624,6 +624,18 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
self.white_space = style.get_inherited_text().white_space;
}
fn processing_br_element(&self) -> bool {
self.inline_box_state_stack
.last()
.map(|state| {
state
.base_fragment_info
.flags
.contains(FragmentFlags::IS_BR_ELEMENT)
})
.unwrap_or(false)
}
/// Start laying out a particular [`InlineBox`] into line items. This will push
/// a new [`InlineBoxContainerState`] onto [`Self::inline_box_state_stack`].
fn start_inline_box(&mut self, inline_box: &InlineBox) {
@ -1114,12 +1126,22 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
// Defer the actual line break until we've cleared all ending inline boxes.
self.linebreak_before_new_content = true;
// We need to ensure that the appropriate space for a linebox is created even if there
// was no other content on this line. We mark the line as having content (needing a
// advance) and having at least the height associated with this nesting of inline boxes.
self.current_line
.max_block_size
.max_assign(&self.current_line_max_block_size_including_nested_containers());
// In quirks mode, the line-height isn't automatically added to the line. If we consider a
// forced line break a kind of preserved white space, quirks mode requires that we add the
// line-height of the current element to the line box height.
//
// The exception here is `<br>` elements. They are implemented with `pre-line` in Servo, but
// this is an implementation detail. The "magic" behavior of `<br>` elements is that they
// add line-height to the line conditionally: only when they are on an otherwise empty line.
let line_is_empty =
!self.current_line_segment.has_content && !self.current_line.has_content;
if !self.processing_br_element() || line_is_empty {
let strut_size = self
.current_inline_container_state()
.strut_block_sizes
.clone();
self.update_unbreakable_segment_for_new_content(&strut_size, Length::zero(), false);
}
self.had_inflow_content = true;
}
@ -1275,7 +1297,10 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
/// Commit the current unbrekable segment to the current line. In addition, this will
/// place all floats in the unbreakable segment and expand the line dimensions.
fn commit_current_segment_to_line(&mut self) {
if self.current_line_segment.line_items.is_empty() {
// The line segments might have no items and have content after processing a forced
// linebreak on an empty line.
if self.current_line_segment.line_items.is_empty() && !self.current_line_segment.has_content
{
return;
}

View file

@ -77,8 +77,10 @@ bitflags! {
/// Flags used to track various information about a DOM node during layout.
#[derive(Clone, Copy, Debug, Serialize)]
pub(crate) struct FragmentFlags: u8 {
/// Whether or not this node is a body element on an HTML document.
/// Whether or not the node that created this fragment is a `<body>` element on an HTML document.
const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 0b00000001;
/// Whether or not the node that created this Fragment is a `<br>` element.
const IS_BR_ELEMENT = 0b00000010;
}
}

View file

@ -747,6 +747,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ThreadSafeLayoutElement<'dom>
self.element
}
fn get_local_name(&self) -> &LocalName {
self.element.local_name()
}
fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
self.element.get_attr_enum(namespace, name)
}

View file

@ -345,6 +345,10 @@ pub trait ThreadSafeLayoutElement<'dom>:
/// lazily_compute_pseudo_element_style, which operates on TElement.
unsafe fn unsafe_get(self) -> Self::ConcreteElement;
/// Get the local name of this element. See
/// <https://dom.spec.whatwg.org/#concept-element-local-name>.
fn get_local_name(&self) -> &LocalName;
fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>;
fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>;