Skip to content

Commit

Permalink
LibWeb: Improve html/body size quirks
Browse files Browse the repository at this point in the history
  • Loading branch information
Psychpsyo committed Jan 19, 2025
1 parent 400aefb commit 6a8912c
Show file tree
Hide file tree
Showing 276 changed files with 829 additions and 638 deletions.
78 changes: 72 additions & 6 deletions Libraries/LibWeb/Layout/BlockFormattingContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,30 +496,96 @@ void BlockFormattingContext::resolve_used_height_if_treated_as_auto(Box const& b
height = max(height, calculate_inner_height(box, available_space, computed_values.min_height()));
}

// https://quirks.spec.whatwg.org/#the-html-element-fills-the-viewport-quirk
// 3.6. The html element fills the viewport quirk
// In quirks mode, if the document element element matches the following conditions:
if (box.document().in_quirks_mode()
// - element is an html element.
&& box.dom_node()
&& box.dom_node()->is_html_html_element()
&& box.computed_values().height().is_auto()) {
// 3.6. The html element fills the viewport quirk
// https://quirks.spec.whatwg.org/#the-html-element-fills-the-viewport-quirk
// FIXME: Handle vertical writing mode.
// - The computed value of the width property of element is auto and element has a vertical writing mode,
&& ((box.computed_values().width().is_auto() && box.text_flow_direction() == Gfx::Orientation::Vertical)
// or the computed value of the height property of element is auto and element has a horizontal writing mode.
|| (box.computed_values().height().is_auto() && box.text_flow_direction() == Gfx::Orientation::Horizontal))) {
// ...then element must have its border box size in the block flow direction set using the following algorithm:

// 1. Let margins be sum of the used values of the margin-left and margin-right properties of element
// if element has a vertical writing mode, otherwise let margins be the sum of the used values of
// the margin-top and margin-bottom properties of element.
auto margins = box_state.margin_top + box_state.margin_bottom;
CSSPixels margins;
if (box.text_flow_direction() == Gfx::Orientation::Vertical) {
margins = box_state.margin_left + box_state.margin_right;
} else {
margins = box_state.margin_top + box_state.margin_bottom;
}

// 2. Let size be the size of the initial containing block in the block flow direction minus margins.
auto size = box_state.containing_block_used_values()->content_height() - margins;

// 3. Return the bigger value of size and the normal border box size the element would have
// according to the CSS specification.
height = max(size, height);
// NOTE: We deal in content box size, not border box size so we need to subtract borders and padding here.
CSSPixels borders;
if (box.text_flow_direction() == Gfx::Orientation::Vertical) {
borders = computed_values.border_left().width + computed_values.border_right().width + computed_values.padding().left().to_px(box, box_state.containing_block_used_values()->content_width()) + computed_values.padding().right().to_px(box, box_state.containing_block_used_values()->content_width());
} else {
borders = computed_values.border_top().width + computed_values.border_bottom().width + computed_values.padding().top().to_px(box, box_state.containing_block_used_values()->content_height()) + computed_values.padding().bottom().to_px(box, box_state.containing_block_used_values()->content_height());
}
height = max(size - borders, height);

// NOTE: The height of the root element when affected by this quirk is considered to be definite.
box_state.set_has_definite_height(true);
}

// https://quirks.spec.whatwg.org/#the-body-element-fills-the-html-element-quirk
// 3.7. The body element fills the html element quirk

// In quirks mode, if the document’s body element body is not null and if it matches the following conditions:
if (box.document().in_quirks_mode()
&& box.dom_node()
&& box.dom_node() == box.document().body()
// - The computed value of the width property of body is auto and body has a vertical writing mode,
&& ((box.computed_values().width().is_auto() && box.text_flow_direction() == Gfx::Orientation::Vertical)
// or the computed value of the height property of body is auto and body has a horizontal writing mode.
|| (box.computed_values().height().is_auto() && box.text_flow_direction() == Gfx::Orientation::Horizontal))
// - The computed value of the position property of body is not absolute or fixed.
&& !(box.computed_values().position() == CSS::Positioning::Absolute || box.computed_values().position() == CSS::Positioning::Fixed)
// - The computed value of the float property of body is none.
&& box.computed_values().float_() == CSS::Float::None
// - body is not an inline-level element.
&& !box.is_inline()
// - body is not a multi-column spanning element.
// FIXME: Implement this. Note: multiple columns seem to be required for this, so just reading the computed value of column-span is not enough.
) {
// ...then body must have its border box size in the block flow direction set using the following algorithm:

// 1. Let margins be the sum of the used values of the margin-left and margin-right properties of body if body has a vertical writing mode, otherwise let margins be the sum of the
// used values of the margin-top and margin-bottom properties of body.
CSSPixels margins;
if (box.text_flow_direction() == Gfx::Orientation::Vertical) {
margins = box_state.margin_left + box_state.margin_right;
} else {
margins = box_state.margin_top + box_state.margin_bottom;
}

// 2. Let size be the size of body’s parent element’s content box in the block flow direction minus margins.
auto size = box_state.containing_block_used_values()->content_height() - margins;

// 3. Return the bigger value of size and the normal border box size the element would have according to the CSS specification.
// NOTE: We deal in content box size, not border box size so we need to subtract borders and padding here.
CSSPixels borders;
if (box.text_flow_direction() == Gfx::Orientation::Vertical) {
borders = computed_values.border_left().width + computed_values.border_right().width + computed_values.padding().left().to_px(box, box_state.containing_block_used_values()->content_width()) + computed_values.padding().right().to_px(box, box_state.containing_block_used_values()->content_width());
} else {
borders = computed_values.border_top().width + computed_values.border_bottom().width + computed_values.padding().top().to_px(box, box_state.containing_block_used_values()->content_height()) + computed_values.padding().bottom().to_px(box, box_state.containing_block_used_values()->content_height());
}
height = max(size - borders, height);

// What should happen if the html and the body have different writing modes?
// NOTE: Since that is probably rare, we'll just treat the body's height as definite for now when affected by this quirk.
box_state.set_has_definite_height(true);
}

box_state.set_content_height(height);
}

Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibWeb/Layout/BlockFormattingContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class BlockFormattingContext : public FormattingContext {

void measure_scrollable_overflow(Box const&, CSSPixels& bottom_edge, CSSPixels& right_edge) const;

Gfx::Orientation block_flow_direction() const;

enum class FloatSide {
Left,
Right,
Expand Down
16 changes: 16 additions & 0 deletions Libraries/LibWeb/Layout/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1280,4 +1280,20 @@ CSS::UserSelect Node::user_select_used_value() const
return computed_value;
}

// https://drafts.csswg.org/css-writing-modes-4/#text-flow
Gfx::Orientation Node::text_flow_direction() const
{
switch (computed_values().writing_mode()) {
case CSS::WritingMode::HorizontalTb:
return Gfx::Orientation::Horizontal;
case CSS::WritingMode::VerticalRl:
case CSS::WritingMode::VerticalLr:
case CSS::WritingMode::SidewaysRl:
case CSS::WritingMode::SidewaysLr:
return Gfx::Orientation::Vertical;
default:
VERIFY_NOT_REACHED();
}
}

}
3 changes: 3 additions & 0 deletions Libraries/LibWeb/Layout/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ class Node
// https://drafts.csswg.org/css-ui/#propdef-user-select
CSS::UserSelect user_select_used_value() const;

// https://drafts.csswg.org/css-writing-modes-4/#text-flow
Gfx::Orientation text_flow_direction() const;

[[nodiscard]] bool has_been_wrapped_in_table_wrapper() const { return m_has_been_wrapped_in_table_wrapper; }
void set_has_been_wrapped_in_table_wrapper(bool value) { m_has_been_wrapped_in_table_wrapper = value; }

Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibWeb/Painting/BackgroundPainting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static RefPtr<DisplayList> compute_text_clip_paths(PaintContext& context, Painta
fragment_absolute_rect.x().to_float(),
fragment_absolute_rect.y().to_float() + fragment.baseline().to_float(),
} * scale;
display_list_recorder.draw_text_run(baseline_start, *glyph_run, Gfx::Color::Black, fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
display_list_recorder.draw_text_run(baseline_start, *glyph_run, Gfx::Color::Black, fragment_absolute_device_rect.to_type<int>(), scale, paintable.layout_node().text_flow_direction());
};

paintable.for_each_in_inclusive_subtree([&](auto& paintable) {
Expand Down
4 changes: 2 additions & 2 deletions Libraries/LibWeb/Painting/PaintableBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -692,14 +692,14 @@ void paint_text_fragment(PaintContext& context, TextPaintable const& paintable,
fragment_absolute_rect.x().to_float(),
fragment_absolute_rect.y().to_float() + fragment.baseline().to_float(),
} * scale;
painter.draw_text_run(baseline_start, *glyph_run, paintable.computed_values().webkit_text_fill_color(), fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
painter.draw_text_run(baseline_start, *glyph_run, paintable.computed_values().webkit_text_fill_color(), fragment_absolute_device_rect.to_type<int>(), scale, paintable.layout_node().text_flow_direction());

auto selection_rect = context.enclosing_device_rect(fragment.selection_rect()).to_type<int>();
if (!selection_rect.is_empty()) {
painter.fill_rect(selection_rect, CSS::SystemColor::highlight(paintable.computed_values().color_scheme()));
DisplayListRecorderStateSaver saver(painter);
painter.add_clip_rect(selection_rect);
painter.draw_text_run(baseline_start, *glyph_run, CSS::SystemColor::highlight_text(paintable.computed_values().color_scheme()), fragment_absolute_device_rect.to_type<int>(), scale, fragment.orientation());
painter.draw_text_run(baseline_start, *glyph_run, CSS::SystemColor::highlight_text(paintable.computed_values().color_scheme()), fragment_absolute_device_rect.to_type<int>(), scale, paintable.layout_node().text_flow_direction());
}

paint_text_decoration(context, paintable, fragment);
Expand Down
23 changes: 4 additions & 19 deletions Libraries/LibWeb/Painting/PaintableFragment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ int PaintableFragment::text_index_at(CSSPixelPoint position) const
return 0;

CSSPixels relative_inline_offset = [&]() {
switch (orientation()) {
switch (layout_node().text_flow_direction()) {
case Gfx::Orientation::Horizontal:
return position.x() - absolute_rect().x();
case Gfx::Orientation::Vertical:
Expand Down Expand Up @@ -102,7 +102,7 @@ CSSPixelRect PaintableFragment::range_rect(size_t start_offset, size_t end_offse
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;

auto rect = absolute_rect();
switch (orientation()) {
switch (layout_node().text_flow_direction()) {
case Gfx::Orientation::Horizontal:
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
rect.set_width(pixel_width_of_selection);
Expand All @@ -128,7 +128,7 @@ CSSPixelRect PaintableFragment::range_rect(size_t start_offset, size_t end_offse
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;

auto rect = absolute_rect();
switch (orientation()) {
switch (layout_node().text_flow_direction()) {
case Gfx::Orientation::Horizontal:
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
rect.set_width(pixel_width_of_selection);
Expand All @@ -154,7 +154,7 @@ CSSPixelRect PaintableFragment::range_rect(size_t start_offset, size_t end_offse
auto pixel_width_of_selection = CSSPixels::nearest_value_for(font.width(text.substring_view(selection_start_in_this_fragment, selection_end_in_this_fragment - selection_start_in_this_fragment))) + 1;

auto rect = absolute_rect();
switch (orientation()) {
switch (layout_node().text_flow_direction()) {
case Gfx::Orientation::Horizontal:
rect.set_x(rect.x() + pixel_distance_to_first_selected_character);
rect.set_width(pixel_width_of_selection);
Expand All @@ -172,21 +172,6 @@ CSSPixelRect PaintableFragment::range_rect(size_t start_offset, size_t end_offse
return {};
}

Gfx::Orientation PaintableFragment::orientation() const
{
switch (m_writing_mode) {
case CSS::WritingMode::HorizontalTb:
return Gfx::Orientation::Horizontal;
case CSS::WritingMode::VerticalRl:
case CSS::WritingMode::VerticalLr:
case CSS::WritingMode::SidewaysRl:
case CSS::WritingMode::SidewaysLr:
return Gfx::Orientation::Vertical;
default:
VERIFY_NOT_REACHED();
}
}

CSSPixelRect PaintableFragment::selection_rect() const
{
if (auto const* focused_element = paintable().document().focused_element(); focused_element && is<HTML::FormAssociatedTextControlElement>(*focused_element)) {
Expand Down
4 changes: 2 additions & 2 deletions Tests/LibWeb/Layout/expected/Element-insertAdjacentHTML.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x211 children: not-inline
BlockContainer <body> at (8,8) content-size 784x584 children: not-inline
BlockContainer <div> at (8,8) content-size 784x17 children: inline
frag 0 from TextNode start: 0, length: 38, rect: [8,8 312.4375x17] baseline: 13.296875
"This is inserted before the container."
Expand Down Expand Up @@ -28,7 +28,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline

ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x211]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x584]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x17]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer<DIV>#container) [28,45 744x137]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x0 children: inline
BlockContainer <body> at (8,8) content-size 784x584 children: inline
BlockContainer <div> at (8,550) content-size 100x50 positioned [BFC] children: not-inline
TextNode <#text>

ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x0]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x584]
PaintableWithLines (BlockContainer<DIV>) [8,550 100x50]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (1,1) content-size 798x600 [BFC] children: not-inline
BlockContainer <body> at (10,10) content-size 780x0 children: inline
BlockContainer <html> at (1,1) content-size 798x598 [BFC] children: not-inline
BlockContainer <body> at (10,10) content-size 780x580 children: inline
TextNode <#text>
BlockContainer <div.only-t-l> at (6,6) content-size 10x10 positioned [BFC] children: not-inline
TextNode <#text>
Expand All @@ -9,9 +9,9 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <div.both> at (11,11) content-size 10x10 positioned [BFC] children: not-inline
TextNode <#text>

ViewportPaintable (Viewport<#document>) [0,0 800x600] overflow: [0,0 800x602]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x602]
PaintableWithLines (BlockContainer<BODY>) [9,9 782x2]
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [9,9 782x582]
PaintableWithLines (BlockContainer<DIV>.only-t-l) [5,5 12x12]
PaintableWithLines (BlockContainer<DIV>.only-mt-ml) [15,15 12x12]
PaintableWithLines (BlockContainer<DIV>.both) [10,10 12x12]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x34 children: not-inline
BlockContainer <body> at (8,8) content-size 784x584 children: not-inline
BlockContainer <div> at (8,8) content-size 784x34 children: not-inline
BlockContainer <(anonymous)> at (8,8) content-size 784x0 children: inline
TextNode <#text>
Expand Down Expand Up @@ -44,7 +44,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline

ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x34]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x584]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x34]
PaintableWithLines (BlockContainer(anonymous)) [8,8 784x0]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x0]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x110 children: not-inline
BlockContainer <html> at (0,0) content-size 800x692 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x584 children: not-inline
BlockContainer <div> at (8,8) content-size 784x110 children: not-inline
BlockContainer <div.square.white> at (8,8) content-size 100x100 floating [BFC] children: not-inline
BlockContainer <div.clearfix> at (8,108) content-size 10x10 children: not-inline
BlockContainer <div.square.black> at (8,218) content-size 49x49 floating [BFC] children: not-inline

ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x110] overflow: [8,8 784x259]
ViewportPaintable (Viewport<#document>) [0,0 800x600] overflow: [0,0 800x692]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x692]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x584]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x110] overflow: [8,8 784x259]
PaintableWithLines (BlockContainer<DIV>.square.white) [8,8 100x100]
PaintableWithLines (BlockContainer<DIV>.clearfix) [8,108 10x10]
Expand Down
Loading

0 comments on commit 6a8912c

Please sign in to comment.