LibWeb: Keep flushing layout dirtied during layout

The layout loop added for container queries intentionally caps repeated
style/layout passes, since each layout can enqueue more size-query style
work. That cap should still apply, but absence of post-layout style work
does not by itself mean the document is clean.

Only leave the loop when there is no pending post-layout style work and
layout is up to date. Otherwise spend another capped pass flushing the
layout-only invalidation instead of falling through to the final
layout_is_up_to_date() verification.

Add crash coverage for a clientHeight read after nested container query
invalidations, matching the synchronous layout flush path.
This commit is contained in:
Andreas Kling 2026-05-24 16:47:19 +02:00 committed by Andreas Kling
parent 2419079374
commit 8d491d72f0
2 changed files with 70 additions and 1 deletions

View file

@ -1903,7 +1903,12 @@ void Document::update_layout(UpdateLayoutReason reason)
}
}
if (!needs_style_update_after_layout())
if (needs_style_update_after_layout())
continue;
// The pass cap above bounds repeated container query style/layout cycles.
// Layout-only invalidations still need to be flushed before we can exit.
if (layout_is_up_to_date())
break;
}

View file

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html class="test-wait">
<style>
.container {
width: 100px;
height: 20px;
container-type: size;
}
#level0 {
container-name: level0;
}
#level1 { container-name: level1; }
#level2 { container-name: level2; }
#level3 { container-name: level3; }
#level4 { container-name: level4; }
#level5 { container-name: level5; }
#level6 { container-name: level6; }
#level7 { container-name: level7; }
#level8 { container-name: level8; }
#level9 { container-name: level9; }
#level10 { container-name: level10; }
@container level0 (width = 200px) { #level1 { width: 200px; } }
@container level1 (width = 200px) { #level2 { width: 200px; } }
@container level2 (width = 200px) { #level3 { width: 200px; } }
@container level3 (width = 200px) { #level4 { width: 200px; } }
@container level4 (width = 200px) { #level5 { width: 200px; } }
@container level5 (width = 200px) { #level6 { width: 200px; } }
@container level6 (width = 200px) { #level7 { width: 200px; } }
@container level7 (width = 200px) { #level8 { width: 200px; } }
@container level8 (width = 200px) { #level9 { width: 200px; } }
@container level9 (width = 200px) { #level10 { width: 200px; } }
</style>
<body>
<div id="level0" class="container">
<div id="level1" class="container">
<div id="level2" class="container">
<div id="level3" class="container">
<div id="level4" class="container">
<div id="level5" class="container">
<div id="level6" class="container">
<div id="level7" class="container">
<div id="level8" class="container">
<div id="level9" class="container">
<div id="level10" class="container">PASS</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
setTimeout(() => {
level0.style.width = "200px";
document.body.clientHeight;
document.documentElement.classList.remove("test-wait");
}, 0);
</script>