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>;

View file

@ -0,0 +1,2 @@
[line-height-preserved-segment-break.html]
expected: FAIL

View file

@ -340751,6 +340751,19 @@
{}
]
],
"line-height-preserved-segment-break.html": [
"8ad2651f4b999091996682be72745e31e0ce559a",
[
null,
[
[
"/css/reference/ref-filled-green-100px-square.xht",
"=="
]
],
{}
]
],
"line-height-quirks-mode.html": [
"9e9baf62226dfccc15f81b6d22edb87a29e2d313",
[

View file

@ -0,0 +1,2 @@
[quirks-mode-br-line-height.html]
expected: FAIL

View file

@ -5402,6 +5402,19 @@
{}
]
],
"quirks-mode-br-line-height.html": [
"ecb49833f809e72cd57772e7a98085e787cb8baa",
[
null,
[
[
"/_mozilla/css/quirks-mode-br-line-height-ref.html",
"=="
]
],
{}
]
],
"quotes_none_a.html": [
"c37ff23e9084d7d198b3c97e14d2e00ab417dd6c",
[
@ -9673,6 +9686,10 @@
"0195f154cf3c4303e5aaf4fc9e7cfa358b8386d7",
[]
],
"quirks-mode-br-line-height-ref.html": [
"2108a4ae5e1f823cf2a3923070d54c42487fd8c6",
[]
],
"quotes_none_ref.html": [
"85f3cf14ca2da71a45efd75803e84e7b5dc23a85",
[]

View file

@ -0,0 +1,17 @@
<link rel="author" href="mrobinson@igalia.com">
<style>
span {
display: inline-block;
width: 50px;
height: 50px;
background: green;
color: transparent;
}
</style>
<body>
<div style="width: 100px; border: solid; line-height: 50px;">
<span></span><br>
<span style="background: transparent; color: transparent;">A</span>
<span></span>
</div>
</body>

View file

@ -0,0 +1,35 @@
<!-- quirks mode -->
<link rel="match" href="quirks-mode-br-line-height-ref.html">
<link rel="author" href="mrobinson@igalia.com">
<!--
Normally in quirks, only text and uncollapsible white space in inline boxes
adds the inline box line height to the line. A forced line break is a kind of
uncollapsible white space, so it should add line height to the line box.
We implement <br> in Servo with `white-space: pre-line`, but <br> doesn't act
like a preserved line break. It only adds its line height to the line if its
on an empty line.
This behavior doesn't seem to be specified, so is part of the "magic" behavior
of <br> elements.
-->
<style>
span {
display: inline-block;
width: 50px;
height: 50px;
background: green;
color: transparent;
}
</style>
<body>
<div style="width: 100px; border: solid; line-height: 50px;">
<span>A</span>
<!-- The first <br> ends the line and the second <br> adds 50px
of block size, because it inherits `line-height` from the
parent <div> -->
<br>
<br>
<span>A</span>
</div>
</body>

View file

@ -0,0 +1,24 @@
<!-- quirks mode -->
<link rel="author" title="Martin Robinson" href="mrobinson@igalia.com">
<link rel="help" href="https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk">
<link rel="help" href="https://drafts.csswg.org/css-text/#valdef-white-space-pre-line">
<link rel="match" href="../css/reference/ref-filled-green-100px-square.xht">
<style>
.container {
width: 100px;
background: green;
white-space: pre-line;
}
.inline-box {
line-height: 100px;
}
</style>
<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
<div style="width: 100px; height: 100px; background: red;">
<!-- The preserved segment break in the span should mean that "The line height calculation
quirk" does not apply. -->
<div class="container"><span class="inline-box">&#xA;</span></div>
</div>