LibWeb: Recompute child style when parent's display changes

When a parent element's display property changes (e.g., to flex or
grid), children may need to be blockified or un-blockified.
Previously, children only received a recompute_inherited_style() call
which doesn't run the blockification logic.

This patch adds a parent_display_changed flag to the recursive style
update that forces children to get a full style recompute when their
parent's display change triggers a layout tree rebuild.
This commit is contained in:
Andreas Kling 2026-01-26 07:13:06 +01:00 committed by Andreas Kling
parent 5fc276872a
commit 3b90eb1d49
4 changed files with 45 additions and 9 deletions

View file

@ -1515,7 +1515,7 @@ void Document::update_layout(UpdateLayoutReason reason)
}
}
[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node, CSS::StyleComputer& style_computer, bool needs_inherited_style_update, bool recompute_elements_depending_on_custom_properties)
[[nodiscard]] static CSS::RequiredInvalidationAfterStyleChange update_style_recursively(Node& node, CSS::StyleComputer& style_computer, bool needs_inherited_style_update, bool recompute_elements_depending_on_custom_properties, bool parent_display_changed)
{
bool const needs_full_style_update = node.document().needs_full_style_update();
CSS::RequiredInvalidationAfterStyleChange invalidation;
@ -1532,7 +1532,7 @@ void Document::update_layout(UpdateLayoutReason reason)
CSS::RequiredInvalidationAfterStyleChange node_invalidation;
if (is<Element>(node)) {
auto& element = static_cast<Element&>(node);
if (needs_full_style_update || node.needs_style_update() || (recompute_elements_depending_on_custom_properties && element.style_uses_var_css_function())) {
if (needs_full_style_update || node.needs_style_update() || parent_display_changed || (recompute_elements_depending_on_custom_properties && element.style_uses_var_css_function())) {
node_invalidation = element.recompute_style(did_change_custom_properties);
} else if (needs_inherited_style_update) {
node_invalidation = element.recompute_inherited_style();
@ -1559,11 +1559,14 @@ void Document::update_layout(UpdateLayoutReason reason)
}
bool children_need_inherited_style_update = !invalidation.is_none();
if (needs_full_style_update || node.child_needs_style_update() || children_need_inherited_style_update || recompute_elements_depending_on_custom_properties) {
// NB: When display changes to/from flex/grid/contents, children may need to be blockified or un-blockified.
// This requires a full style recompute, not just inherited style update.
bool children_need_full_style_recompute = node_invalidation.rebuild_layout_tree;
if (needs_full_style_update || node.child_needs_style_update() || children_need_inherited_style_update || recompute_elements_depending_on_custom_properties || children_need_full_style_recompute) {
if (node.is_element()) {
if (auto shadow_root = static_cast<DOM::Element&>(node).shadow_root()) {
if (needs_full_style_update || shadow_root->needs_style_update() || shadow_root->child_needs_style_update()) {
auto subtree_invalidation = update_style_recursively(*shadow_root, style_computer, children_need_inherited_style_update, recompute_elements_depending_on_custom_properties);
auto subtree_invalidation = update_style_recursively(*shadow_root, style_computer, children_need_inherited_style_update, recompute_elements_depending_on_custom_properties, children_need_full_style_recompute);
if (!is_display_none)
invalidation |= subtree_invalidation;
}
@ -1571,8 +1574,8 @@ void Document::update_layout(UpdateLayoutReason reason)
}
node.for_each_child([&](auto& child) {
if (needs_full_style_update || child.needs_style_update() || children_need_inherited_style_update || child.child_needs_style_update() || recompute_elements_depending_on_custom_properties) {
auto subtree_invalidation = update_style_recursively(child, style_computer, children_need_inherited_style_update, recompute_elements_depending_on_custom_properties);
if (needs_full_style_update || child.needs_style_update() || children_need_inherited_style_update || child.child_needs_style_update() || recompute_elements_depending_on_custom_properties || children_need_full_style_recompute) {
auto subtree_invalidation = update_style_recursively(child, style_computer, children_need_inherited_style_update, recompute_elements_depending_on_custom_properties, children_need_full_style_recompute);
if (!is_display_none)
invalidation |= subtree_invalidation;
}
@ -1628,7 +1631,7 @@ void Document::update_style()
build_registered_properties_cache();
auto invalidation = update_style_recursively(*this, style_computer(), false, false);
auto invalidation = update_style_recursively(*this, style_computer(), false, false, false);
if (!invalidation.is_none())
invalidate_display_list();

View file

@ -0,0 +1,4 @@
Initial child display: inline
Color after flex: rgb(0, 0, 0)
Display after flex: block
Display after block: inline

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail Dynamic changes to `display` causing blockification of children are handled correctly
1 Pass
Pass Dynamic changes to `display` causing blockification of children are handled correctly

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<link rel="author" href="mailto:kling@ladybird.org">
<script src="../include.js"></script>
<style>
#container { display: block; }
</style>
<div id="container">
<span id="child">test</span>
</div>
<script>
test(() => {
const container = document.getElementById("container");
const child = document.getElementById("child");
// Initially, span should be inline
println(`Initial child display: ${getComputedStyle(child).display}`);
// Change parent to flex - child should get blockified
container.style.display = "flex";
// Query color first (doesn't need layout) to trigger only update_style()
println(`Color after flex: ${getComputedStyle(child).color}`);
println(`Display after flex: ${getComputedStyle(child).display}`);
// Change parent back to block - child should un-blockify
container.style.display = "block";
println(`Display after block: ${getComputedStyle(child).display}`);
});
</script>