Firefox · Layout
CVE-2026-117954
Logic Error in Layout
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
iftesting/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html |
modified | |
fortesting/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html |
modified |
Files Changed
layout/generic/nsFlexContainerFrame.cpplayout/generic/nsGridContainerFrame.cpptesting/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
Patch
diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp
index 253ba3adadc..2ff3648ed39 100644
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -4675,7 +4675,8 @@ void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext,
nsReflowStatus& aStatus) {
NormalizeChildLists();
- if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ if (IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
+ !GetNextContinuation()) {
return;
}
@@ -5625,7 +5626,7 @@ std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
const LogicalSize& aAvailableSizeForItems,
const LogicalMargin& aBorderPadding, const FlexboxAxisTracker& aAxisTracker,
FlexLayoutResult& aFlr, PerFragmentFlexData& aFragmentData) {
- if (HidesContentForLayout()) {
+ if (HidesContentForLayout() && !GetNextContinuation()) {
return {0, nsReflowStatus()};
}
diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp
index 5189f3f6f31..1699a429103 100644
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -9275,7 +9275,7 @@ nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aGridRI,
nscoord bSize = aContentArea.BSize(wm);
MOZ_ASSERT(aGridRI.mReflowInput);
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
- if (HidesContentForLayout()) {
+ if (HidesContentForLayout() && !GetNextContinuation()) {
return bSize;
}
@@ -9444,7 +9444,8 @@ void nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
nsReflowStatus& aStatus) {
NormalizeChildLists();
- if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+ if (IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
+ !GetNextContinuation()) {
return;
}
diff --git a/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
new file mode 100644
index 00000000000..adb42689f6c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<meta charset="utf-8">
+<title>Hiding fragmented absolute children with escaped fixed descendants</title>
+<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-breaking">
+<style>
+html, body { margin: 0 }
+.cols {
+ width: 620px;
+ height: 120px;
+ columns: 3;
+ column-gap: 10px;
+ column-fill: auto;
+}
+.outer { position: relative; contain: layout style paint }
+.normal { height: 700px }
+.victim {
+ position: absolute;
+ inset: 0 auto auto 0;
+ width: 170px;
+ display: flex;
+ flex-flow: column nowrap;
+}
+.item { flex: 0 0 auto; margin: 0; line-height: 17px; font-size: 12px }
+.row { height: 21px }
+.fx { position: fixed; inset: 2px auto auto 2px; width: 13px; height: 13px }
+.grid .victim { display: grid; grid-template-rows: repeat(7, 120px) }
+.grid .item { grid-row: 1 / -1; min-height: 0 }
+.hide-self .victim { contain: layout style paint }
+.hide-self.flex .item { flex-basis: 756px; min-height: 0; height: 756px }
+.case[popover] {
+ position: fixed;
+ inset: 0 auto auto 0;
+ margin: 0;
+ border: 0;
+ padding: 0;
+}
+.case::backdrop { display: none }
+.fx[popover] { margin: 0; border: 0; padding: 0 }
+</style>
+<body>
+<template id="fixture">
+ <div class="cols"><div class="outer">
+ <div class="normal">normal</div>
+ <div class="victim"><div class="item"></div></div>
+ </div></div>
+</template>
+<script>
+function runTest(layout, topLayer, hideSelf) {
+ const test = document.createElement("div");
+ test.classList.add("case", layout);
+ if (hideSelf) {
+ test.classList.add("hide-self");
+ }
+ test.append(document.getElementById("fixture").content.cloneNode(true));
+ const cols = test.querySelector(".cols");
+ const outer = test.querySelector(".outer");
+ const victim = test.querySelector(".victim");
+ test.querySelector(".item").innerHTML = Array.from({length: 36}, (_, i) =>
+ '<div class="row">row ' + i +
+ (i === 20 ? '<span class="fx">x</span>' : '') + '</div>').join('');
+ const fixed = test.querySelector(".fx");
+ if (topLayer) {
+ test.setAttribute("popover", "manual");
+ fixed.setAttribute("popover", "manual");
+ }
+ document.body.append(test);
+ if (topLayer) {
+ test.showPopover();
+ fixed.showPopover();
+ }
+
+ // Create the flex/grid and item continuations, with the fixed descendant's
+ // placeholder in a later fragment. In the top-layer case, the fixed frame
+ // escapes even a paint-containing victim to the viewport's absolute list.
+ victim.getClientRects();
+ const hidden = hideSelf ? victim : outer;
+ hidden.style.contentVisibility = "hidden";
+ cols.offsetHeight;
+ hidden.style.contentVisibility = "visible";
+ cols.offsetHeight;
+ fixed.getClientRects();
+
+ if (topLayer) {
+ fixed.hidePopover();
+ test.hidePopover();
+ }
+ test.remove();
+}
+
+addEventListener("load", () => {
+ for (const layout of ["flex", "grid"]) {
+ runTest(layout, false, false);
+ runTest(layout, true, false);
+ runTest(layout, true, true);
+ }
+ document.documentElement.classList.remove("test-wait");
+});
+</script>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
new file mode 100644
index 00000000000..adb42689f6c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<meta charset="utf-8">
+<title>Hiding fragmented absolute children with escaped fixed descendants</title>
+<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-breaking">
+<style>
+html, body { margin: 0 }
+.cols {
+ width: 620px;
+ height: 120px;
+ columns: 3;
+ column-gap: 10px;
+ column-fill: auto;
+}
+.outer { position: relative; contain: layout style paint }
+.normal { height: 700px }
+.victim {
+ position: absolute;
+ inset: 0 auto auto 0;
+ width: 170px;
+ display: flex;
+ flex-flow: column nowrap;
+}
+.item { flex: 0 0 auto; margin: 0; line-height: 17px; font-size: 12px }
+.row { height: 21px }
+.fx { position: fixed; inset: 2px auto auto 2px; width: 13px; height: 13px }
+.grid .victim { display: grid; grid-template-rows: repeat(7, 120px) }
+.grid .item { grid-row: 1 / -1; min-height: 0 }
+.hide-self .victim { contain: layout style paint }
+.hide-self.flex .item { flex-basis: 756px; min-height: 0; height: 756px }
+.case[popover] {
+ position: fixed;
+ inset: 0 auto auto 0;
+ margin: 0;
+ border: 0;
+ padding: 0;
+}
+.case::backdrop { display: none }
+.fx[popover] { margin: 0; border: 0; padding: 0 }
+</style>
+<body>
+<template id="fixture">
+ <div class="cols"><div class="outer">
+ <div class="normal">normal</div>
+ <div class="victim"><div class="item"></div></div>
+ </div></div>
+</template>
+<script>
+function runTest(layout, topLayer, hideSelf) {
+ const test = document.createElement("div");
+ test.classList.add("case", layout);
+ if (hideSelf) {
+ test.classList.add("hide-self");
+ }
+ test.append(document.getElementById("fixture").content.cloneNode(true));
+ const cols = test.querySelector(".cols");
+ const outer = test.querySelector(".outer");
+ const victim = test.querySelector(".victim");
+ test.querySelector(".item").innerHTML = Array.from({length: 36}, (_, i) =>
+ '<div class="row">row ' + i +
+ (i === 20 ? '<span class="fx">x</span>' : '') + '</div>').join('');
+ const fixed = test.querySelector(".fx");
+ if (topLayer) {
+ test.setAttribute("popover", "manual");
+ fixed.setAttribute("popover", "manual");
+ }
+ document.body.append(test);
+ if (topLayer) {
+ test.showPopover();
+ fixed.showPopover();
+ }
+
+ // Create the flex/grid and item continuations, with the fixed descendant's
+ // placeholder in a later fragment. In the top-layer case, the fixed frame
+ // escapes even a paint-containing victim to the viewport's absolute list.
+ victim.getClientRects();
+ const hidden = hideSelf ? victim : outer;
+ hidden.style.contentVisibility = "hidden";
+ cols.offsetHeight;
+ hidden.style.contentVisibility = "visible";
+ cols.offsetHeight;
+ fixed.getClientRects();
+
+ if (topLayer) {
+ fixed.hidePopover();
+ test.hidePopover();
+ }
+ test.remove();
+}
+
+addEventListener("load", () => {
+ for (const layout of ["flex", "grid"]) {
+ runTest(layout, false, false);
+ runTest(layout, true, false);
+ runTest(layout, true, true);
+ }
+ document.documentElement.classList.remove("test-wait");
+});
+</script>
Loading diff…
References
On This Page